Composite and Iterator Design Pattern Combination in TypeScript

When ever you’re dealing with hierarchical structures requiring efficient traversal, then think of the powerful combination of the Iterator Pattern an the Composite Pattern. Here, the Composite Pattern represents individual objects and collections as tree-like structures and treats them uniformly. Take a case for an office furniture catalog with categories like chairs, desks, and storage units, each containing multiple subcategories or products. Once we introduce in the Iterator Pattern, we simplify a way to navigate through these nested subcategories without exposing the underlying implementation. This means there is no need to write recursive loops when accessing furniture items nested within. The good thing here is abstracting the traversal logic while sequentially retrieving each product no matter its depth in the hierarchy. It would be great if we also implemented it when building our custom package in Laravel 11
Here’s how you’d want to combine them:
Scenario: Managing Office Furniture Hierarchy
- Composite Pattern: Represents office furniture store as a tree structure where
Workstation
(composite) containsDesks
,Chairs
, andCabinets
(leaf nodes). - Iterator Pattern: The guy to easy traverse our furniture hierarchy without exposing its internal structure.
Step 1: Define the Composite Pattern
1.1 Base Component Interface
// Component interface representing office furniture
interface OfficeFurniture {
getName(): string;
getPrice(): number;
}
1.2 Leaf Components (Individual Furniture Items)
Desk Class
// Concrete class representing a Desk
class Desk implements OfficeFurniture {
constructor(private name: string, private price: number) {}
getName(): string {
return this.name;
}
getPrice(): number {
return this.price;
}
}
Chair Class
// Concrete class representing a Chair
class Chair implements OfficeFurniture {
constructor(private name: string, private price: number) {}
getName(): string {
return this.name;
}
getPrice(): number {
return this.price;
}
}
Cabinet Class
// Concrete class representing a Cabinet
class Cabinet implements OfficeFurniture {
constructor(private name: string, private price: number) {}
getName(): string {
return this.name;
}
getPrice(): number {
return this.price;
}
}
1.3 Composite Class (Workstation to Group Multiple Furniture Items)
// Composite class representing a Workstation
class Workstation implements OfficeFurniture {
private furnitureItems: OfficeFurniture[] = [];
constructor(private name: string) {}
add(furniture: OfficeFurniture): void {
this.furnitureItems.push(furniture);
}
remove(furniture: OfficeFurniture): void {
this.furnitureItems = this.furnitureItems.filter(item => item !== furniture);
}
getName(): string {
return this.name;
}
getPrice(): number {
return this.furnitureItems.reduce((total, item) => total + item.getPrice(), 0);
}
getFurniture(): OfficeFurniture[] {
return this.furnitureItems;
}
}
Step 2: Implement the Iterator Pattern
2.1 Define the Iterator Interface
// Iterator interface for office furniture traversal
interface FurnitureIterator {
hasNext(): boolean;
next(): OfficeFurniture;
}
2.2 Implement the Concrete Iterator
// Concrete iterator to traverse composite furniture structure
class WorkstationIterator implements FurnitureIterator {
private index = 0;
constructor(private furnitureItems: OfficeFurniture[]) {}
hasNext(): boolean {
return this.index < this.furnitureItems.length;
}
next(): OfficeFurniture {
return this.furnitureItems[this.index++];
}
}
2.3 Iterable Collection Interface
// Interface for collections that can create iterators
interface IterableCollection {
createIterator(): FurnitureIterator;
}
2.4 Implement IterableCollection in Workstation
// Workstation class now supports iteration
class IterableWorkstation extends Workstation implements IterableCollection {
createIterator(): FurnitureIterator {
return new WorkstationIterator(this.getFurniture());
}
}
Step 3: Putting It All Together
// Create individual office furniture items
const chair1 = new Chair("ErgoChair", 150);
const chair2 = new Chair("LuxuryChair", 250);
const desk1 = new Desk("StandingDesk", 500);
const cabinet1 = new Cabinet("FileCabinet", 300);
// Create a composite Workstation and add items
const workstation = new IterableWorkstation("Executive Workstation");
workstation.add(chair1);
workstation.add(chair2);
workstation.add(desk1);
workstation.add(cabinet1);
// Create an iterator for the workstation
const iterator = workstation.createIterator();
// Iterate through furniture items in the workstation
console.log(`Furniture in ${workstation.getName()}:`);
while (iterator.hasNext()) {
const furniture = iterator.next();
console.log(`- ${furniture.getName()} (${furniture.getPrice()})`);
}
console.log(`Total Cost: ${workstation.getPrice()}`);
🔹 Expected Output
Furniture in Executive Workstation:
- ErgoChair (150)
- LuxuryChair (250)
- StandingDesk (500)
- FileCabinet (300)
Total Cost: 1200
Why This Combination Works Well
✅ Scalability → New furniture types can be added without modifying existing code.
✅ Flexibility → Workstations can be nested, and iterators traverse them uniformly. It means this, if this system needs to show only in-stock items from a deeply nested structure, a filtering iterator can return relevant products efficiently. This also creates different types of iterators for various needs. These include;
- Breadth-first or depth-first traversal (depending on whether you want to explore categories first or individual products).
- Filtering iterators that return only specific types of products (e.g., all ergonomic chairs).
- Lazy-loading iterators to fetch data on demand, improving performance for large catalogs.
✅ Encapsulation → The iteration logic is separate from the furniture structure.