Decorator Pattern - Dynamic Behavior Extension in TypeScript
Learn the Decorator pattern for adding behavior to objects dynamically. TypeScript examples for logging, caching, authentication, and middleware chains.
Detailed Explanation
Decorator Pattern
The Decorator pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality by wrapping objects in decorator layers.
The Wrapper Concept
Each decorator has the same interface as the object it wraps. It delegates calls to the wrapped object and adds behavior before or after:
interface DataSource {
read(): string;
write(data: string): void;
}
class FileDataSource implements DataSource {
read() { return "raw file data"; }
write(data: string) { /* write to file */ }
}
class EncryptionDecorator implements DataSource {
constructor(private source: DataSource) {}
read() { return decrypt(this.source.read()); }
write(data: string) { this.source.write(encrypt(data)); }
}
class CompressionDecorator implements DataSource {
constructor(private source: DataSource) {}
read() { return decompress(this.source.read()); }
write(data: string) { this.source.write(compress(data)); }
}
Stacking Decorators
The power of the pattern is in stacking: new CompressionDecorator(new EncryptionDecorator(new FileDataSource())) applies both encryption and compression transparently. Order matters: encryption before compression produces different results than compression before encryption.
Comparison with Inheritance
Inheritance is static and applies to the entire class. Decorators are dynamic and apply to individual objects at runtime. You cannot "un-inherit" behavior, but you can choose not to wrap an object.
Real-World Examples
- Express/Koa middleware: Each middleware wraps the next, adding logging, authentication, compression, etc.
- React Higher-Order Components:
withAuth(withTheme(MyComponent))follows the decorator pattern exactly. - Java I/O Streams:
new BufferedReader(new InputStreamReader(new FileInputStream(...)))is the textbook Decorator example.
When to Prefer Decorator
Use Decorator when you need to combine behaviors at runtime without class explosion. If you have 3 features (A, B, C) that can be combined, inheritance requires 8 subclasses (A, B, C, AB, AC, BC, ABC, none). Decorators need only 3 wrapper classes.
Use Case
Decorator is essential in middleware pipelines (HTTP request processing), logging and monitoring wrappers, caching layers that wrap data access objects, input validation chains, and React component enhancement with Higher-Order Components or render props.