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) { /* email */ }
}
class SmsDecorator implements Notifier {
private final Notifier wrapped;
SmsDecorator(Notifier wrapped) { this.wrapped = wrapped; }
public void send(String message) {
wrapped.send(message);
/* sms */
}
}
TypeScript example
interface Notifier {
send(message: string): void;
}
class EmailNotifier implements Notifier {
send(message: string): void {}
}
class SmsDecorator implements Notifier {
constructor(private wrapped: Notifier) {}
send(message: string): void {
this.wrapped.send(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.