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
wrapped by →
wrapped by →
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
MilkDecorator
+$0.50
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.ioreaders, 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".
Back to all topics →