Introduction to Composite Pattern
Composite lets you compose objects into tree structures and treat individual objects and compositions uniformly.
The Problem
In logistics, packages can contain other packages:
typescript1// Without Composite - awkward handling2function calculateWeight(item: Package | Box | Pallet): number {3 if (item instanceof Package) {4 return item.weight;5 } else if (item instanceof Box) {6 let total = item.boxWeight;7 for (const pkg of item.packages) {8 total += calculateWeight(pkg);9 }10 return total;11 } else if (item instanceof Pallet) {12 let total = item.palletWeight;13 for (const box of item.boxes) {14 total += calculateWeight(box);15 }16 return total;17 }18 return 0;19}2021// Client must know about every type22// Adding Container means updating all functions
The Solution: Composite
Treat individual items and groups uniformly:
typescript1// Common interface2interface Shippable {3 getWeight(): number;4 getDescription(): string;5}67// Leaf - individual package8class Package implements Shippable {9 constructor(10 private name: string,11 private weight: number12 ) {}1314 getWeight(): number {15 return this.weight;16 }1718 getDescription(): string {19 return this.name;20 }21}2223// Composite - can contain other Shippables24class Box implements Shippable {25 private items: Shippable[] = [];2627 constructor(private boxWeight: number = 0.5) {}2829 add(item: Shippable): void {30 this.items.push(item);31 }3233 getWeight(): number {34 return this.boxWeight + this.items.reduce(35 (sum, item) => sum + item.getWeight(),36 037 );38 }3940 getDescription(): string {41 const contents = this.items.map(i => i.getDescription()).join(', ');42 return `Box containing: [${contents}]`;43 }44}4546// Usage - treat everything the same47const laptop = new Package('Laptop', 2.5);48const charger = new Package('Charger', 0.3);49const box = new Box();50box.add(laptop);51box.add(charger);5253console.log(laptop.getWeight()); // 2.554console.log(box.getWeight()); // 3.3 (0.5 + 2.5 + 0.3)
Pattern Structure
1 ┌─────────────────┐2 │ Component │3 │ (interface) │4 │─────────────────│5 │ + operation() │6 └────────┬────────┘7 │8 ┌────────────────┴────────────────┐9 │ │10 ┌────────▼────────┐ ┌────────▼────────┐11 │ Leaf │ │ Composite │12 │─────────────────│ │─────────────────│13 │ + operation() │ │ - children[] │14 └─────────────────┘ │ + operation() │15 │ + add(child) │16 │ + remove(child) │17 └─────────────────┘
Key Participants
- Component - Common interface for all objects in composition
- Leaf - Represents individual objects with no children
- Composite - Stores child components and implements child-related operations
Nested Structure Example
typescript1// Deeply nested structure2const electronicsPallet = new Pallet();34const laptopBox = new Box();5laptopBox.add(new Package('Laptop', 2.5));6laptopBox.add(new Package('Power Adapter', 0.3));78const phoneBox = new Box();9phoneBox.add(new Package('Phone', 0.2));10phoneBox.add(new Package('Earbuds', 0.05));1112electronicsPallet.add(laptopBox);13electronicsPallet.add(phoneBox);1415// One call handles entire tree16const totalWeight = electronicsPallet.getWeight();
When to Use Composite
- Tree structures - Part-whole hierarchies
- Uniform treatment - Clients should ignore difference between compositions and leaves
- Recursive operations - Operations that apply to entire structure
- Nested containers - Boxes within boxes, folders within folders
Logistics Applications
| Composite | Leaves | Use Case |
|---|---|---|
| Pallet | Boxes | Warehouse stacking |
| Container | Pallets | Shipping container |
| Order | Line Items | Order totals |
| Route | Stops | Delivery planning |
Summary
Composite enables treating complex nested structures as single units. Perfect for hierarchical data where you need uniform operations across all levels.