Decorator
Wrap an object to add behavior at runtime — stack multiple wrappers for combined effects — without modifying the original class or creating a subclass.
Intent & Description
🎯 Intent
Attach new responsibilities to an object dynamically at runtime instead of baking them into subclasses at compile time.
📋 Context
You have a Coffee class and want to support Milk, Sugar, Whip, Syrup add-ons in any combination. Creating a subclass for every combination (MilkCoffee, SugarMilkCoffee, WhipSugarMilkCoffee…) is obviously insane. You need a composable approach.
💡 Solution
Decorators implement the same interface as the wrapped object and hold a reference to it. Each Decorator’s method calls the wrapped object’s method and adds its own behavior before or after. Stack Decorators to combine effects: new WhipDecorator(new MilkDecorator(new Coffee())).
Real-world Use Case
Source
📌 TL;DR
Wrap an object, call through, add behavior. Stack wrappers for combined effects. Far more flexible than inheritance for combinatorial behavior. Debugging deeply nested stacks is painful.
Advantages
- Add and remove responsibilities at runtime without touching the original class.
- Combine behaviors by stacking decorators — far more flexible than inheritance.
Disadvantages
- Deeply stacked decorators are hard to debug — hard to tell which layer is misbehaving.
- Removing a specific decorator from the middle of a stack is awkward.
class Coffee {
getCost() { return 10; }
getDescription() { return "Simple coffee"; }
}
// Decorator
class MilkDecorator {
constructor(coffee) { this.coffee = coffee; }
getCost() { return this.coffee.getCost() + 2; }
getDescription() { return this.coffee.getDescription() + ", milk"; }
}
let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
console.log(myCoffee.getCost()); // 12
console.log(myCoffee.getDescription()); // "Simple coffee, milk"