📖 What Is the Decorator Pattern (Simple Explanation)
The Decorator Pattern allows you to dynamically add, extend, or modify the behavior of an object without changing its original code.
You “wrap” the original object with one or more decorators that add extra functionality.
🎨 Real-World Examples (Easy to Visualize)
☕️ 1. Coffee Shop (Starbucks-style)
- Base: Black coffee (basic)
-
Decorators:
- Milk
- Sugar
- Whipped cream
- Vanilla shot
You can stack decorators:
A Black Coffee + Milk + Sugar + Whipped Cream
Each adds behavior (cost, description) without changing the Coffee class.
📦 2. Parcel Delivery
- Base: Boxed item
-
Decorators:
- Bubble wrap
- Gift wrap
- Insurance
- Express Delivery
You can combine them in any order — without modifying the Item class.
🧑💼 3. User Permissions
- Base User: Can login
-
Decorators:
- Admin rights
- Report viewer
- Billing access
Each decorator adds abilities dynamically to the user without rewriting the User class.
🖥️ TypeScript Code Example — Coffee Shop ☕️
Let’s model the coffee example using the Decorator Pattern in TypeScript.
// 1. Component Interface
interface Coffee {
cost(): number;
description(): string;
}
// 2. Concrete Component
class BasicCoffee implements Coffee {
cost() {
return 5;
}
description() {
return "Black Coffee";
}
}
// 3. Abstract Decorator
class CoffeeDecorator implements Coffee {
constructor(protected coffee: Coffee) {}
cost() {
return this.coffee.cost();
}
description() {
return this.coffee.description();
}
}
// 4. Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 1.5;
}
description() {
return this.coffee.description() + ", Milk";
}
}
class SugarDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 0.5;
}
description() {
return this.coffee.description() + ", Sugar";
}
}
// 5. Usage
let myCoffee: Coffee = new BasicCoffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);
console.log(myCoffee.description()); // Black Coffee, Milk, Sugar
console.log("$" + myCoffee.cost()); // $7
🎯 Why Use Decorator Pattern?
✅ Open/Closed Principle — extend behavior without modifying existing code
✅ Flexible combinations — you can mix and match multiple decorators
✅ Avoid inheritance overuse — adding too many subclasses is messy
✅ Wrap functionality dynamically — unlike hardcoded behaviors
✅ Summary Table
Concept | Base | Decorators |
---|---|---|
Coffee | Black Coffee | Milk, Sugar, Cream, Vanilla |
User | Basic User | Admin, Billing, Reporting |
Parcel | Item | Gift wrap, Insurance, Express |
API | Raw request | Logging, Caching, Retry logic |
📌 Common Real-World API Use
- Axios Interceptors → a real decorator chain for logging, caching, error handling
- Express Middleware → like decorators for HTTP requests
- React Higher-Order Components (HOCs) → decorators wrapping components
✅ Quick Concept Diagram
BasicCoffee
↓
+ MilkDecorator
↓
+ SugarDecorator
↓
Final Decorated Coffee (new behavior)
Each layer wraps the last, adding new behavior.