← Back to Home
Decorator Pattern
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
What is it?
The Decorator pattern lets you wrap objects with new behavior at runtime without modifying the original class.
Example
interface Coffee {
cost: () => number;
description: () => string;
}
class BasicCoffee implements Coffee {
cost() {
return 5;
}
description() {
return 'Basic Coffee';
}
}
class MilkDecorator implements Coffee {
constructor(private coffee: Coffee) {}
cost() {
return this.coffee.cost() + 2;
}
description() {
return this.coffee.description() + ', Milk';
}
}
class SugarDecorator implements Coffee {
constructor(private coffee: Coffee) {}
cost() {
return this.coffee.cost() + 1;
}
description() {
return this.coffee.description() + ', Sugar';
}
}
const coffee = new SugarDecorator(new MilkDecorator(new BasicCoffee()));
console.log(coffee.description()); // "Basic Coffee, Milk, Sugar"
console.log(coffee.cost()); // 8Common Uses
- Adding features to components dynamically
- UI components (e.g., wrappers around buttons, inputs)
- Middleware chains (e.g., Express, Redux)
When to Use
- You want to add responsibilities to objects without subclassing
- Need to mix and match behaviors at runtime
Caution
- Too many small decorator classes can complicate maintenance
- Harder to trace functionality than with inheritance