Skip to main content

Decorator

Overview​

Attach additional responsibilities to an object dynamically by wrapping it.

When to use​

  • You need flexible, runtime composition of behavior.
  • Inheritance would lead to too many subclasses.

Java example​

interface Notifier {
void send(String message);
}

class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Email: " + message);
}
}

class SmsDecorator implements Notifier {
private final Notifier wrapped;

SmsDecorator(Notifier wrapped) {
this.wrapped = wrapped;
}

public void send(String message) {
wrapped.send(message);
System.out.println("SMS: " + message);
}
}

TypeScript example​

interface Notifier {
send(message: string): void;
}

class EmailNotifier implements Notifier {
send(message: string): void {
console.log(`Email: ${message}`);
}
}

class SmsDecorator implements Notifier {
constructor(private wrapped: Notifier) {}

send(message: string): void {
this.wrapped.send(message);
console.log(`SMS: ${message}`);
}
}

Pros and cons​

Pros:

  • Behavior can be added without subclassing.
  • Multiple decorators can be combined.

Cons:

  • Many small objects can be hard to debug.
  • Order of decorators matters.

Common pitfalls​

  • Hiding side effects in deep decorator stacks.
  • Mutating shared state across decorators.