📖 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.