skip to content

Composite Pattern - Treat Individual Objects and Groups Uniformly

Updated: at 12:00 AM

1. The Problem

Problem: Treating Individual Objects and Groups Uniformly

Software often models part–whole hierarchies:

Naive approach: distinguish single objects from groups everywhere.

if isinstance(item, File):
    item.render()
elif isinstance(item, Folder):
    for child in item.children:
        child.render()

Problems:

We want to treat a single object and a collection of objects the same way.

2. The Composite Pattern: Uniformity via Recursion

The Composite Pattern lets you compose objects into tree structures and work with them uniformly.

Core idea:

A system using Composite has three parts:

Client code never checks types.

You get:

Use Composite when:

3. Implementation: Component + Leaf + Composite (File System Example)

Component interface

from abc import ABC, abstractmethod

class FileSystemComponent(ABC):
    @abstractmethod
    def show(self, indent=0):
        pass

Leaf: File

class File(FileSystemComponent):
    def __init__(self, name):
        self.name = name

    def show(self, indent=0):
        print(" " * indent + f"File: {self.name}")

Composite: Folder

class Folder(FileSystemComponent):
    def __init__(self, name):
        self.name = name
        self.children = []

    def add(self, component: FileSystemComponent):
        self.children.append(component)

    def remove(self, component: FileSystemComponent):
        self.children.remove(component)

    def show(self, indent=0):
        print(" " * indent + f"Folder: {self.name}")
        for child in self.children:
            child.show(indent + 2)

Client code (uniform)

root = Folder("root")
root.add(File("a.txt"))
root.add(File("b.txt"))

sub = Folder("images")
sub.add(File("logo.png"))
sub.add(File("icon.svg"))

root.add(sub)

root.show()

Client never checks:

Everything responds to show().

4. Why This Works: The Recursive Invariant

Key invariant:

Composite and Leaf share the same interface.

That single fact enables:

The recursion lives inside the composite, not in the client.

Without Composite:

With Composite:

5. Another Example: UI Components

Component

class UIComponent(ABC):
    @abstractmethod
    def render(self):
        pass

Leaf

class Button(UIComponent):
    def render(self):
        print("Render Button")

Composite

class Panel(UIComponent):
    def __init__(self):
        self.children = []

    def add(self, component: UIComponent):
        self.children.append(component)

    def render(self):
        print("Render Panel")
        for child in self.children:
            child.render()

Client

panel = Panel()
panel.add(Button())
panel.add(Button())

panel.render()

Same call. Same interface. Recursive execution.

6. When to Use Composite (and When Not To)

Use Composite when you notice:

Avoid Composite when:

Common pitfalls: