8. Composite
The Composite pattern composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly — a folder and a file both support getSize(), even though a folder delegates to its children.
Intent and Motivation
Intent: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions uniformly.
Motivation: Graphical user interfaces, file systems, organizational charts, and scene graphs are all trees where some nodes are leaves (a button, a text file) and some are containers (a panel, a directory). Without Composite, client code needs if (isLeaf) branches everywhere. Composite defines a common interface (Component) with operations like render(), getSize(), or search() that work recursively on both leaves and composites.
The result is elegant recursive structures where containers transparently delegate to children.
Structure (UML-like)
┌──────────────────┐
│ Component │ (interface)
├──────────────────┤
│ + operation() │
│ + add(Component) │
│ + remove(Component)│
│ + getChild(int) │
└────────▲─────────┘
│
┌────┴─────┐
│ │
┌───┴────┐ ┌──┴─────────┐
│ Leaf │ │ Composite │
├────────┤ ├────────────┤
│+operation│ │ - children: │
│ │ │ List<Comp>│
└────────┘ │ + operation()│ (delegates to children)
│ + add() │
└─────────────┘
Participants:
- Component — common interface for leaves and composites.
- Leaf — represents end objects with no children.
- Composite — stores child components and implements child-related operations.
- Client — manipulates objects through the Component interface.
Java Example
import java.util.*;
interface FileSystemNode {
String getName();
long getSize();
}
class File implements FileSystemNode {
private final String name;
private final long size;
File(String name, long size) {
this.name = name;
this.size = size;
}
public String getName() { return name; }
public long getSize() { return size; }
}
class Directory implements FileSystemNode {
private final String name;
private final List<FileSystemNode> children = new ArrayList<>();
Directory(String name) { this.name = name; }
void add(FileSystemNode node) { children.add(node); }
public String getName() { return name; }
public long getSize() {
return children.stream().mapToLong(FileSystemNode::getSize).sum();
}
}
// Usage
Directory root = new Directory("root");
root.add(new File("readme.txt", 1024));
Directory src = new Directory("src");
src.add(new File("Main.java", 4096));
root.add(src);
System.out.println(root.getName() + " size: " + root.getSize()); // 5120
JavaScript Example
class MenuComponent {
constructor(name) { this.name = name; }
add(child) { throw new Error('Not supported'); }
getPrice() { throw new Error('Not supported'); }
}
class MenuItem extends MenuComponent {
constructor(name, price) {
super(name);
this.price = price;
}
getPrice() { return this.price; }
}
class Menu extends MenuComponent {
constructor(name) {
super(name);
this.children = [];
}
add(child) { this.children.push(child); }
getPrice() {
return this.children.reduce((sum, c) => sum + c.getPrice(), 0);
}
}
const menu = new Menu('Restaurant');
const drinks = new Menu('Drinks');
drinks.add(new MenuItem('Coffee', 3.50));
drinks.add(new MenuItem('Tea', 2.50));
menu.add(drinks);
menu.add(new MenuItem('Burger', 12.00));
console.log('Total:', menu.getPrice()); // 18.00
Real-World Use Cases
| Framework / System | Usage |
|---|---|
| Java Swing / JavaFX | Container holds Component children; panels, frames, and buttons share a common API. |
| React | Component trees — parent components render child components recursively. |
| HTML DOM | Elements are composites; document.body.appendChild() builds a tree. |
| File systems | Directories contain files and subdirectories; du sums sizes recursively. |
| AWS Organizations | Organizational units (composites) contain accounts (leaves) and sub-units. |
| Scene graphs (Unity, Three.js) | GameObject nodes form hierarchies with transform inheritance. |
Pros and Cons
| Pros | Cons |
|---|---|
| Uniform treatment of leaves and composites simplifies client code | Can make the design overly general — not all components need child management |
| Easy to add new component types (new leaf or composite classes) | Hard to restrict which components can be children of which (type safety) |
| Natural fit for recursive tree structures | Operations like getSize() on deep trees can be expensive without caching |
| Simplifies client code — no type checking for leaf vs composite | Removing or moving subtrees requires careful parent-child bookkeeping |
| Supports complex nested hierarchies cleanly | Iterator and traversal logic can become complex with cycles (if allowed) |
When to Use vs When NOT to Use
Use when:
- You need to represent a part-whole hierarchy of objects.
- You want clients to ignore the difference between compositions and individual objects.
- The structure can be represented as a tree (or forest).
- Operations must propagate through the entire structure recursively.
Do NOT use when:
- The structure is flat with no nesting — a simple list suffices.
- Leaf and composite behaviors are fundamentally different (forcing a common interface is awkward).
- You need graph structures with cycles or cross-references (Composite assumes a tree).
- Performance of recursive traversal is critical and caching is not feasible.
Common Mistakes
- Putting child-management methods on Leaf — leaves should reject
add()/remove()(throw or no-op with clear semantics). - Breaking Liskov Substitution — composites and leaves must honor the same contract for shared operations.
- Allowing cycles — a composite adding itself or its ancestor causes infinite recursion.
- No type safety — anything can be added to any composite; use validation or typed composites when needed.
- Eager deep operations without caching — recalculating
getSize()on every call is O(n); cache and invalidate on change.
Related Patterns
- Iterator — traverses Composite structures; often used together for depth-first or breadth-first walks.
- Visitor — adds operations across a Composite tree without modifying component classes.
- Decorator — also uses recursive composition, but adds behavior to a single object, not part-whole trees.
- Chain of Responsibility — handlers can form a composite-like chain, but without the part-whole semantics.
- Flyweight — optimizes memory when many leaf nodes share intrinsic state in large trees.