Composite Pattern
Composite Pattern
Compose objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions of objects uniformly.
Intent
The Composite pattern defines a Component interface (or abstract class) that both Leaf and Composite classes implement. A Leaf node represents a primitive element with no children — it performs the actual work. A Composite node manages a collection of child Component objects and delegates its operation to them, typically iterating over children and aggregating their results. The critical property is uniformity: client code works exclusively through the Component type and never needs to distinguish a leaf from a composite. This allows recursive structures where a composite can contain other composites to arbitrary depth.
Common use cases: file system trees (File/Directory both implement FileSystemNode), UI component trees (Button/Panel both implement Widget), organisation charts (Employee/Department both implement OrgNode), expression abstract syntax trees (Literal/BinaryExpr both implement Expr).
When NOT to Use
- The structure is flat (no nesting ever occurs) — a plain list or array is clearer and less indirected
- Leaf and Composite operations are substantially different — a common interface becomes a leaky abstraction that forces unimplemented methods on Leaf or Composite nodes
- Only one or two levels of nesting exist — the added indirection is not worth it for shallow structures; model explicitly instead
- Operations on leaves and composites need genuinely different signatures — the common interface cannot accommodate both without becoming awkward
When to Use
- The structure is naturally hierarchical and recursively nested (trees of arbitrary depth)
- Clients must treat leaf nodes and container nodes identically — uniform treatment via a common interface is the goal
- You want to add new kinds of components (new Leaf or Composite types) without changing client code
How It Works
Participants:
- Component — the interface (or abstract class) that declares the key operation all nodes must implement. Defines the type used by all client code.
- Leaf — implements Component; has no children; performs the actual work when operation() is called.
- Composite — implements Component; manages a
children: Component[]collection withadd(c: Component)and optionallyremove(); delegatesoperation()to each child and aggregates results. - Client — uses only the Component type; calls
operation()without knowing whether it is talking to a Leaf or a Composite.
The recursive structure is key: because Composite.children is typed as Component[], a child can itself be a Composite, enabling tree structures of arbitrary depth. Composite.operation() typically iterates over children and delegates, making the entire tree compute itself bottom-up.
Class Diagram
classDiagram
class Component {
<<interface>>
+operation()*
}
class Leaf {
+operation()
}
class Composite {
-children : Component[]
+operation()
+add(child : Component)
+remove(child : Component)
+getChild(index) Component
}
Component <|.. Leaf
Component <|.. Composite
Composite o-- Component : children (recursive)
TypeScript Example
// Composite — TypeScript
// Source: GoF Design Patterns, p.163
interface Component {
operation(): string;
}
class Leaf implements Component {
constructor(private name: string) {}
operation(): string { return this.name; }
}
class Composite implements Component {
private children: Component[] = [];
add(c: Component) { this.children.push(c); }
operation(): string {
return `[${this.children.map(c => c.operation()).join(", ")}]`;
}
}
// Usage — client uses only the Component type
const root = new Composite();
root.add(new Leaf("A"));
const branch = new Composite();
branch.add(new Leaf("B"));
branch.add(new Leaf("C"));
root.add(branch);
console.log(root.operation()); // [A, [B, C]]Java Example
// Composite — Java
// Source: GoF Design Patterns, p.163
interface Component { String operation(); }
class Leaf implements Component {
private final String name;
Leaf(String n) { this.name = n; }
@Override public String operation() { return name; }
}
class Composite implements Component {
private final List<Component> children = new ArrayList<>();
void add(Component c) { children.add(c); }
@Override public String operation() {
return children.stream().map(Component::operation)
.collect(Collectors.joining(", ", "[", "]"));
}
}Lineage Forward
The Composite pattern is the structural ancestor of the DDD Aggregate pattern (see Aggregate-Pattern, created Phase 10). An Aggregate is a consistency-boundary cluster over an object graph that shares Composite's part-whole structure but adds invariant enforcement — the Aggregate Root controls all access to the cluster, ensuring the cluster's business rules are never violated by external modification.
Related Concepts
| Pattern | Relationship |
|---|---|
| Flyweight-Pattern | Flyweight is often used inside Composite leaf nodes to reduce memory cost when many near-identical leaves exist |
| Decorator-Pattern | Both wrap Component objects; Decorator adds behaviour to a single object, Composite manages a tree of objects |
| Iterator-Pattern | Often used to traverse a Composite tree (created Phase 9) |
| Aggregate-Pattern | DDD Aggregate is a Composite with consistency-boundary semantics (created Phase 10) |
Related Frontend Patterns
- Atomic-Design-Pattern — Brad Frost's atomic design hierarchy is the Composite pattern applied to UI: atoms are Leaf nodes, molecules and organisms are Composite nodes, the page is the root of the tree. Clients (page layouts) treat atoms and organisms through the same Component interface.
- Component-Composition-Patterns — Compound components (e.g.,
<Select><Option>) are a direct implementation of Composite: the parent (Select) manages a collection of children (Option) through shared implicit state via React Context.
Sources
- Gamma, Helm, Johnson, Vlissides — Design Patterns, Addison-Wesley, 1994, p.163
Backlinks
Notes that link here: GoF-Patterns-MOC