Composite Pattern — Tree Structures of Objects
What You'll Learn
You will learn how the Composite pattern lets you build recursive tree structures where leaves and containers share a common interface, simplifying client code. You will understand the trade-off between transparency and safety in Composite design.
Why It Matters
Hierarchies appear everywhere — UI render trees, organisational charts, file systems, and menu systems. Without Composite, clients must distinguish between leaf and container nodes with instanceof checks. Composite eliminates this by uniting both under a single component interface. Consider a file system where you want to calculate the total size: without Composite, you'd recursively walk directories with conditional logic for files versus directories. With Composite, you call get_size() on any node and let polymorphism handle the rest.
Real-World Use
A graphic editor treats both simple shapes and groups of shapes identically for dragging, resizing, and rendering. DodaTech's workflow designer uses Composite to build pipelines where a single step or an entire sub-pipeline can be validated, timed, and monitored the same way — a sub-pipeline looks exactly like a single step to the monitoring system.
The Pattern
Component declares the common interface. Leaf represents end objects. Composite stores child components and implements child-related operations.
from abc import ABC, abstractmethod
class FileSystemComponent(ABC):
@abstractmethod
def get_size(self) -> int:
pass
@abstractmethod
def display(self, indent: str = "") -> str:
pass
class File(FileSystemComponent):
def __init__(self, name: str, size: int):
self._name = name
self._size = size
def get_size(self) -> int:
return self._size
def display(self, indent: str = "") -> str:
return f"{indent}{self._name} ({self._size} bytes)"
class Directory(FileSystemComponent):
def __init__(self, name: str):
self._name = name
self._children: list[FileSystemComponent] = []
def add(self, component: FileSystemComponent):
self._children.append(component)
def remove(self, component: FileSystemComponent):
self._children.remove(component)
def get_size(self) -> int:
return sum(c.get_size() for c in self._children)
def display(self, indent: str = "") -> str:
lines = [f"{indent}{self._name}/"]
for child in self._children:
lines.append(child.display(indent + " "))
return "\n".join(lines)
root = Directory("root")
docs = Directory("docs")
docs.add(File("readme.md", 200))
docs.add(File("license.md", 50))
src = Directory("src")
src.add(File("main.py", 1024))
src.add(File("utils.py", 512))
root.add(docs)
root.add(src)
print(root.display())
print(f"Total size: {root.get_size()} bytes")
root/
docs/
readme.md (200 bytes)
license.md (50 bytes)
src/
main.py (1024 bytes)
utils.py (512 bytes)
Total size: 1786 bytes
Structure
classDiagram
class Component {
<>
+operation()
+add(Component)
+remove(Component)
+getChild(int)
}
class Leaf {
+operation()
}
class Composite {
+operation()
+add(Component)
+remove(Component)
+getChild(int)
}
Leaf ..|> Component
Composite ..|> Component
Composite --> "*" Component : children
Real-World Usage
- React component tree — every component, whether a
<div>leaf or a<App>Composite, exposes the same render API. - Java AWT/Swing
Container—Container.add(Component)accepts both simple widgets and nested containers. - XML/HTML DOM — every node implements
Node, andElementnodes can contain child nodes. - File system APIs —
os.walk()in Python treats directories and files through the same iteration protocol.
Related Patterns
- Decorator has a similar structure but adds behaviour rather than aggregating children.
- Iterator is used to traverse Composite structures.
- Visitor lets you define operations on Composite elements without changing their classes.
- Flyweight helps share leaf data across large Composite trees.
Pros and Cons
| Pros | Cons |
|---|---|
| Makes client code simple and uniform | Can violate the Interface Segregation Principle with overly general components |
| Easy to add new component types | Tree traversal can become expensive for deep hierarchies |
| Natural fit for recursive structures | Adding type-specific behaviour may require instanceof checks |
| Transparent to clients whether they work with leaves or composites | Child ordering and indexing adds complexity |
The code demonstrates the beautiful simplicity of Composite. Both File and Directory implement FileSystemComponent. A Directory can contain any number of FileSystemComponent objects — including other Directory instances. When display() or get_size() is called on the root, the call propagates recursively through the entire tree, with each node handling its own contribution.
Practice Questions
- How would you modify Composite to support a parent reference from child to container? What would this enable?
- Implement a safety policy — disallow adding children to a Leaf at compile time.
- What Caching strategies can optimise repeated
get_size()calls in a stable tree where files rarely change? - Combine Composite with Visitor to implement an export operation across all nodes.
Challenge
Add a SymLink class that delegates to another FileSystemComponent. This introduces a different kind of leaf that can point to files or directories, potentially creating cycles. How would you handle this?
Real-World Task
Examine your UI component hierarchy. Identify deeply nested conditional logic that distinguishes between container and leaf nodes. Refactor it using Composite and use DodaTech's tree visualiser to verify the hierarchy.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro