Posts

Decorator

June 22, 2025
Decorator Pattern
“Attach new behaviors to objects dynamically by placing them inside wrapper objects that contain the behavior.”Gang of Four (GoF)

You’re building a notification library. You start with:
Ts
class Notifier {
  send(message: string): void {
    // send email
  }
}
Later, clients want more:
  • SMS notifications
  • Facebook messages
  • Slack alerts
If you use subclasses (e.g., EmailNotifier, SMSNotifier, SlackNotifier), you'll need to create combinations like EmailAndSMSNotifier, EmailSMSSlackNotifier, etc. That leads to a combinatorial explosion — not scalable or maintainable.
Use the Decorator Pattern to wrap behavior dynamically at runtime:
  • All decorators implement the same interface.
  • They contain a reference to the next component in the chain.
  • Each decorator can augment behavior before or after delegating.

Ts
interface Notifier {
  send(message: string): void;
}
Ts
class EmailNotifier implements Notifier {
  send(message: string): void {
    console.log(`Email: ${message}`);
  }
}
Ts
abstract class NotifierDecorator implements Notifier {
  constructor(protected wrappee: Notifier) {}

  send(message: string): void {
    this.wrappee.send(message);
  }
}
Ts
class SMSNotifier extends NotifierDecorator {
  send(message: string): void {
    super.send(message);
    console.log(`SMS: ${message}`);
  }
}
Ts
class SlackNotifier extends NotifierDecorator {
  send(message: string): void {
    super.send(message);
    console.log(`Slack: ${message}`);
  }
}

Ts
const baseNotifier = new EmailNotifier();
const smsNotifier = new SMSNotifier(baseNotifier);
const fullNotifier = new SlackNotifier(smsNotifier);

fullNotifier.send("🚨 Alert: Server down!");
Email: 🚨 Alert: Server down!
SMS: 🚨 Alert: Server down!
Slack: 🚨 Alert: Server down!

  • You need to add responsibilities dynamically.
  • You want to avoid subclassing for each feature combination.
  • You want to follow the Open/Closed Principle.

  • Behavior added at runtime without modifying classes.
  • Combine behaviors flexibly via composition.
  • Follows Single Responsibility Principle: split logic into modular decorators.
  • Hard to debug or track deeply nested decorators.
  • The order of decorators can affect behavior.
  • Initial configuration can be complex and verbose.
On this page