20 minlesson

Introduction to Composite Pattern

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:

typescript
1// Without Composite - awkward handling
2function 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}
20
21// Client must know about every type
22// Adding Container means updating all functions

The Solution: Composite

Treat individual items and groups uniformly:

typescript
1// Common interface
2interface Shippable {
3 getWeight(): number;
4 getDescription(): string;
5}
6
7// Leaf - individual package
8class Package implements Shippable {
9 constructor(
10 private name: string,
11 private weight: number
12 ) {}
13
14 getWeight(): number {
15 return this.weight;
16 }
17
18 getDescription(): string {
19 return this.name;
20 }
21}
22
23// Composite - can contain other Shippables
24class Box implements Shippable {
25 private items: Shippable[] = [];
26
27 constructor(private boxWeight: number = 0.5) {}
28
29 add(item: Shippable): void {
30 this.items.push(item);
31 }
32
33 getWeight(): number {
34 return this.boxWeight + this.items.reduce(
35 (sum, item) => sum + item.getWeight(),
36 0
37 );
38 }
39
40 getDescription(): string {
41 const contents = this.items.map(i => i.getDescription()).join(', ');
42 return `Box containing: [${contents}]`;
43 }
44}
45
46// Usage - treat everything the same
47const laptop = new Package('Laptop', 2.5);
48const charger = new Package('Charger', 0.3);
49const box = new Box();
50box.add(laptop);
51box.add(charger);
52
53console.log(laptop.getWeight()); // 2.5
54console.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

  1. Component - Common interface for all objects in composition
  2. Leaf - Represents individual objects with no children
  3. Composite - Stores child components and implements child-related operations

Nested Structure Example

typescript
1// Deeply nested structure
2const electronicsPallet = new Pallet();
3
4const laptopBox = new Box();
5laptopBox.add(new Package('Laptop', 2.5));
6laptopBox.add(new Package('Power Adapter', 0.3));
7
8const phoneBox = new Box();
9phoneBox.add(new Package('Phone', 0.2));
10phoneBox.add(new Package('Earbuds', 0.05));
11
12electronicsPallet.add(laptopBox);
13electronicsPallet.add(phoneBox);
14
15// One call handles entire tree
16const totalWeight = electronicsPallet.getWeight();

When to Use Composite

  1. Tree structures - Part-whole hierarchies
  2. Uniform treatment - Clients should ignore difference between compositions and leaves
  3. Recursive operations - Operations that apply to entire structure
  4. Nested containers - Boxes within boxes, folders within folders

Logistics Applications

CompositeLeavesUse Case
PalletBoxesWarehouse stacking
ContainerPalletsShipping container
OrderLine ItemsOrder totals
RouteStopsDelivery planning

Summary

Composite enables treating complex nested structures as single units. Perfect for hierarchical data where you need uniform operations across all levels.