Posts

Composite

June 21, 2025
Composite Pattern
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions uniformly.Gang of Four (GoF)

Imagine an e-commerce app where:
  • Product is a single item (e.g., a book).
  • Box is a container that can hold many Products and other Boxes.
If you want to compute the total price of such a nested structure, traversing the whole tree manually is painful. You must constantly check types and navigate hierarchies.
The Composite pattern provides a common interface (Component) for both simple (Leaf) and complex (Composite) objects, allowing the client to treat both uniformly using recursion and polymorphism.
Plaintext
Component (interface)
 ├── Leaf (e.g., Product)
 └── Composite (e.g., Box)
       ├── add()
       ├── remove()
       └── operation()

Ts
interface Item {
  getPrice(): number;
  getName(): string;
}
Ts
class Product implements Item {
  constructor(
    private name: string,
    private price: number,
  ) {}

  getPrice(): number {
    return this.price;
  }

  getName(): string {
    return this.name;
  }
}
Ts
class Box implements Item {
  private children: Item[] = [];

  constructor(private name: string) {}

  add(item: Item): void {
    this.children.push(item);
  }

  remove(item: Item): void {
    this.children = this.children.filter((i) => i !== item);
  }

  getPrice(): number {
    return this.children.reduce((total, item) => total + item.getPrice(), 0);
  }

  getName(): string {
    return this.name;
  }

  listContents(indent = 0): void {
    console.log(`${" ".repeat(indent)}📦 ${this.name} - $${this.getPrice()}`);
    for (const item of this.children) {
      if (item instanceof Box) {
        item.listContents(indent + 2);
      } else {
        console.log(
          `${" ".repeat(indent + 2)}📄 ${item.getName()} - $${item.getPrice()}`,
        );
      }
    }
  }
}
Ts
const book = new Product("Book", 12.99);
const pen = new Product("Pen", 1.99);
const notebook = new Product("Notebook", 4.99);

const smallBox = new Box("Stationery Box");
smallBox.add(pen);
smallBox.add(notebook);

const mainBox = new Box("Main Order");
mainBox.add(book);
mainBox.add(smallBox);

mainBox.listContents();
console.log(`Total Order Price: $${mainBox.getPrice().toFixed(2)}`);

📦 Main Order - $19.97
  📄 Book - $12.99
  📦 Stationery Box - $6.98
    📄 Pen - $1.99
    📄 Notebook - $4.99
Total Order Price: $19.97

  • You need to work with tree structures (e.g., file systems, UI hierarchies, DOM, packaging).
  • You want uniform treatment for individual and grouped objects.
  • You need recursive behavior (like total price, rendering, movement, etc.).

  • Simplifies client code by using polymorphism.
  • Easily extendable: new leaf/composite types don’t break existing code.
  • Recursive traversal logic is clean and centralized.
  • Can introduce unnecessary generalization if leaf and composite behavior differ widely.
  • Makes it harder to enforce type constraints (e.g., ensuring a Box only contains Products).
On this page