1. The Problem
Problem: Treating Individual Objects and Groups Uniformly
Software often models part–whole hierarchies:
- Files and folders
- UI elements (buttons, panels, windows)
- Organization structures (employees, departments)
- Product bundles
- AST nodes
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:
- Client code becomes type-aware
- Branching grows with hierarchy depth
- Violates Open/Closed Principle
- Logic for aggregation leaks into clients
- Recursion spreads everywhere
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:
- Define a common interface for both leaf and composite
- Leaves represent indivisible objects
- Composites contain children of the same interface
- Operations are applied recursively
- Client code treats everything as the same type
A system using Composite has three parts:
- Component — common interface
- Leaf — primitive object
- Composite — object that contains components
Client code never checks types.
You get:
- Uniform treatment of objects and groups
- Recursive composition
- Elimination of conditionals
- Natural modeling of hierarchies
Use Composite when:
- You have part–whole relationships
- You want recursive structures
- Clients shouldn’t care about leaf vs container
- Operations should apply uniformly
- Tree-like data models appear
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:
- Whether something is a file or folder
- Whether recursion is needed
- How children are stored
Everything responds to show().
4. Why This Works: The Recursive Invariant
Key invariant:
Composite and Leaf share the same interface.
That single fact enables:
- Recursive calls
- Structural uniformity
- Arbitrary nesting depth
- Simple client logic
The recursion lives inside the composite, not in the client.
Without Composite:
- Client owns recursion
- Client owns branching
- Client breaks encapsulation
With Composite:
- Structure owns recursion
- Client stays flat
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:
- “This object can contain objects like itself.”
- “Client code keeps checking types.”
- “Operations naturally recurse.”
- “We have tree structures.”
- “Groups and individuals should behave the same.”
Avoid Composite when:
- No hierarchy exists
- Leaf and composite behaviors diverge heavily
- You need strict separation of responsibilities
- Operations differ too much between types
Common pitfalls:
- Making leaf methods meaningless
- Letting composites expose internal children
- Overloading interface with child-management methods
- Deep trees without tail-recursion awareness