Skip to main content

Composite

What This Concept Is

Composite is a structural pattern that lets clients treat individual objects and compositions of objects uniformly by putting both behind a single interface.

The structure:

  • a Component interface declaring operations that make sense for both leaves and groups
  • Leaf classes implementing the operations for primitive elements
  • a Composite class implementing the same interface by delegating to a collection of child Components

Recursion is the point. A Composite holds Components, which may themselves be Composites. The client walks the structure without ever caring which nodes are leaves.

The problem it absorbs: code that keeps branching on "is this one thing or a group of things," spreading the same conditional through every operation.

Why It Matters Here

Composite is how you represent trees with behavior: filesystems, UI widget trees, scene graphs, menus, organizational hierarchies, expression trees. Any time an operation must recurse over a heterogeneous collection that may contain more of itself, Composite is the clean answer.

It also cleans up a specific code smell: the repeated "if leaf do X else recurse" pattern scattered across many methods.

Concrete Example

// Component
interface FsNode {
name(): string;
sizeBytes(): number; // operation meaningful for both
print(indent?: string): void;
}

// Leaf
class File implements FsNode {
constructor(private readonly _name: string, private readonly _size: number) {}
name() { return this._name; }
sizeBytes() { return this._size; }
print(indent = '') { console.log(`${indent}- ${this._name} (${this._size}B)`); }
}

// Composite
class Directory implements FsNode {
private children: FsNode[] = [];
constructor(private readonly _name: string) {}
add(n: FsNode) { this.children.push(n); }
name() { return this._name; }
sizeBytes() { return this.children.reduce((s, c) => s + c.sizeBytes(), 0); }
print(indent = '') {
console.log(`${indent}+ ${this._name}/`);
for (const c of this.children) c.print(indent + ' ');
}
}

const root = new Directory('root');
root.add(new File('readme.md', 120));
const src = new Directory('src');
src.add(new File('main.ts', 800));
root.add(src);
root.print();
console.log(`total: ${root.sizeBytes()}B`);

Structural sketch:

                 +-----------+
| Component |<----------+
+-----------+ |
^ ^ |
| | |children
+------+ +-----------+ |
| Leaf | | Composite |---+
+------+ +-----------+
children: [Component,...]

The recursion on the right-hand side is the whole idea.

Common Confusion / Misconception

  • A tree of objects is not automatically a Composite. Composite requires that leaf and group share the same interface and that clients can treat them uniformly. Without that, you just have a tree data structure.
  • Be honest about "safe vs transparent" trade-offs: putting add(child) on the Component interface makes leaves technically accept children (transparent but unsafe); keeping add only on Composite forces the client to distinguish (safe but less transparent). Head First calls this the safety-versus-transparency trade-off.
  • Composite is not Decorator. Both wrap, but Composite is a tree and Decorator is a chain of one-child wrappers.

How To Use It

  1. Find operations that currently branch on "single item vs group." Those are your Component methods.
  2. Define the Component interface with the operations both leaves and groups should support.
  3. Implement Leaf classes for primitives, with pure logic.
  4. Implement Composite with a children collection and a loop delegating to each child.
  5. Decide consciously whether child management lives on the Component (transparent) or only on the Composite (safe).

Check Yourself

  1. What is the "uniform treatment" property Composite protects, and why do clients care?
  2. What is the safety-versus-transparency trade-off?
  3. When does a tree not want Composite?

Mini Drill or Application

Model a small file system with File (leaf) and Directory (composite). Implement:

  1. sizeBytes() that recurses.
  2. find(predicate) that walks the tree and returns all matches as a flat list.
  3. print(indent) that renders the tree.

Then describe in one paragraph when you would not use Composite -- for example, when leaf and group operations differ too much to share a single interface.

Read This Only If Stuck