Decorator Pattern
The Decorator Pattern is a structural design pattern that allows you to attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
1. The Core Concept
Inheritance is static. You can't add a new method to an object at runtime using inheritance. The Decorator pattern uses Composition to wrap an object with another object that adds functionality.
- Wrapper: The decorator has the same interface as the target object. It delegates requests to the target but can perform actions before or after the delegation.
- Stacking: You can wrap an object in multiple decorators (e.g.,
EncryptionDecorator(CompressionDecorator(DataSource))).
2. Analogy: Clothing
You are a human object.
- If you are cold, you wrap yourself in a
Sweater. - If it's raining, you wrap yourself in a
Raincoat(on top of the sweater). - You are still "You", but your behavior (warmth, dryness) has been extended.
3. Example (TypeScript)
Imagine a notification system.
// The Component Interface
interface Notifier {
send(message: string): void;
}
// Concrete Component
class EmailNotifier implements Notifier {
send(message: string): void {
console.log(`Sending Email: ${message}`);
}
}
// Base Decorator
class BaseDecorator implements Notifier {
protected wrappee: Notifier;
constructor(notifier: Notifier) {
this.wrappee = notifier;
}
send(message: string): void {
this.wrappee.send(message);
}
}
// Concrete Decorator 1
class SMSDecorator extends BaseDecorator {
send(message: string): void {
super.send(message);
console.log(`Sending SMS: ${message}`);
}
}
// Concrete Decorator 2
class SlackDecorator extends BaseDecorator {
send(message: string): void {
super.send(message);
console.log(`Sending Slack: ${message}`);
}
}
// Usage
let stack = new EmailNotifier();
stack = new SMSDecorator(stack);
stack = new SlackDecorator(stack);
stack.send("Hello World!");
// Output:
// Sending Email: Hello World!
// Sending SMS: Hello World!
// Sending Slack: Hello World!
4. Pros and Cons
| Pros | Cons |
|---|---|
| Flexibility: More flexible than inheritance. Add/remove responsibilities at runtime. | Complexity: Can result in many small classes. |
| SRP: You can split a monolithic class into several smaller classes. | Identity: A decorator and its component are not the same object type (if checking instanceof). |
| Combination: Combine behaviors by wrapping objects in multiple decorators. | Configuration: Initializing a stack of wrappers can be ugly code. |
programming/design-patterns programming/composition-over-inheritance programming/proxy-pattern