Decorator Pattern
Structural pattern enabling dynamic addition of functionalities to objects without modifying their base structure.
Updated on January 9, 2026
The Decorator pattern is a structural design pattern that enables extending the functionality of individual objects flexibly and reusably, without affecting other instances of the same class. Unlike inheritance which adds behaviors to an entire class, the decorator wraps the target object in a series of functional layers, each adding specific behavior. This pattern respects the open/closed principle by enabling extension without modifying existing code.
Core Fundamentals
- Composition over inheritance: uses object encapsulation rather than class extension to add behaviors
- Transparency: decorators implement the same interface as the decorated object, enabling interchangeable usage
- Chaining: multiple decorators can stack to combine their functionalities in a modular fashion
- Single responsibility: each decorator has a clear, independent responsibility
Technical and Business Benefits
- Maximum flexibility: add or remove features at runtime without recompilation
- Avoids combinatorial explosion: replaces dozens of subclasses with composable decorators
- Enhanced testability: each decorator can be tested in isolation and in composition
- Reusability: decorators can be applied to different compatible objects
- Simplified maintenance: localized modifications without impacting existing code
Practical TypeScript Example
// Common interface
interface Notifier {
send(message: string): void;
}
// Base component
class EmailNotifier implements Notifier {
send(message: string): void {
console.log(`Email: ${message}`);
}
}
// Abstract decorator
abstract class NotifierDecorator implements Notifier {
constructor(protected wrapped: Notifier) {}
send(message: string): void {
this.wrapped.send(message);
}
}
// Concrete decorators
class SlackDecorator extends NotifierDecorator {
send(message: string): void {
super.send(message);
console.log(`Slack: ${message}`);
}
}
class SMSDecorator extends NotifierDecorator {
send(message: string): void {
super.send(message);
console.log(`SMS: ${message}`);
}
}
class LogDecorator extends NotifierDecorator {
send(message: string): void {
console.log(`[LOG] Sending at ${new Date().toISOString()}`);
super.send(message);
}
}
// Dynamic composition usage
let notifier: Notifier = new EmailNotifier();
notifier = new SlackDecorator(notifier);
notifier = new SMSDecorator(notifier);
notifier = new LogDecorator(notifier);
notifier.send("System alert!");
// Output:
// [LOG] Sending at 2024-01-15T10:30:00.000Z
// Email: System alert!
// Slack: System alert!
// SMS: System alert!Implementation Steps
- Define the common interface shared by base component and decorators
- Implement the concrete base component with fundamental behavior
- Create an abstract decorator class implementing the interface and holding a reference to the wrapped component
- Develop concrete decorators extending the abstract decorator and adding their specific behaviors
- Compose decorators through successive stacking to achieve desired functionality
- Ensure each decorator calls the wrapped component's method (typically via super.method())
Architecture Insight
In modern TypeScript/JavaScript, class decorators (@decorator) offer elegant syntax for specific cases (metadata, logging), but the classic Decorator pattern remains preferable for dynamic runtime behavior composition. Combine both approaches contextually: syntactic decorators for static cross-cutting concerns, Decorator pattern for runtime flexibility.
Common Use Cases
- Streams and I/O: BufferedReader, GZipInputStream in Java wrap basic streams
- HTTP Middleware: Express.js leverages this pattern for middleware chaining
- UI Components: adding borders, scrollbars, shadows to graphical components
- Caching and logging: transparently enriching services with caching or traceability
- Feature flags: conditional activation/deactivation of functionalities
Related Tools and Frameworks
- NestJS: extensive use of decorators for dependency injection and metadata
- TypeORM / TypeGraphQL: decorators for object-relational mapping and GraphQL schemas
- Redux: observable pattern with enhancers that decorate the store
- Java Streams API: functional decorators for data stream transformation
- Python functools.wraps: function decoration for aspect-oriented programming
Common Pitfalls
Excessive decorator stacking can create complex call chains difficult to debug. Clearly document the application order of decorators and their dependencies. Also monitor performance impacts: each layer adds an indirection level. Favor lightweight decorators and consider alternatives (Proxy pattern, AOP) for heavyweight scenarios.
Business Value
The Decorator pattern significantly reduces time-to-market for new features by enabling their addition without major refactoring. Product teams can progressively activate capabilities (A/B testing, canary deployments) through decorator composition. This modularity promotes cross-project reuse, reducing recurring development costs. For critical systems, the ability to dynamically compose behaviors (monitoring, circuit breakers, retry logic) enhances operational resilience while maintaining a clean and maintainable codebase.
