design·lab

Structural pattern

Decorator

Wrap an object in another object that shares its interface — adding behavior at runtime without touching the original class or spawning a subclass for every combination.

✗ The problem

Subclass explosion for every combination

Want coffee with milk? Subclass it. With sugar too? Another subclass. Every new topping multiplies the number of classes you need.

class Coffee { cost(){ return 2 } }
class CoffeeWithMilk { cost(){ return 2.5 } }
class CoffeeWithSugar { cost(){ return 2.25 } }
class CoffeeWithMilkAndSugar { /* … */ }
class CoffeeWithMilkSugarWhip { /* … */ }
// 3 toppings → up to 2³ classes. 5 toppings → 32.
Coffee
+Milk
+Sugar
+Whip
+Milk+Sugar
+Milk+Whip
+Sugar+Whip
+Milk+Sugar+Whip
Every new topping doubles the class count. Unmanageable — and most of these classes are 99% duplicate code.
✓ The pattern

Wrap, don't subclass

A decorator implements the same interface as the object it wraps, forwards the call, then adds its own bit of behavior.

class SimpleCoffee {
  cost() { return 2 }
  desc() { return 'coffee' }
}

class MilkDecorator {
  constructor(inner) { this.inner = inner }
  cost() { return this.inner.cost() + 0.5 }
  desc() { return this.inner.desc() + ' + milk' }
}
SimpleCoffee
$2.00
wrapped by →
MilkDecorator
+$0.50
wrapped by →
SugarDecorator
+$0.25
✓ See it live

Stack decorators — watch cost and description grow

Start from a plain SimpleCoffee. Each button wraps (or unwraps) one decorator around the current object.

coffee
$2.00
✓ Takeaway

Compose behavior, don't inherit it

  • Add responsibilities at runtime — no subclass needed for each combo.
  • Compose freely: any decorator can wrap any object with the same interface.
  • Single Responsibility: each decorator does exactly one small thing.
  • You already use it: Node.js streams, Java java.io readers, React higher-order components, Express middleware chains.
  • Careful: many thin wrappers can be hard to debug, and wrap order can change behavior (e.g. compressing then encrypting ≠ encrypting then compressing).
🎯 Principle applied: Decorator embodies Open/Closed (add behavior without modifying the class), SRP (each decorator = one concern), and "favor composition over inheritance".