Decorator Pattern
Introduction
The Decorator pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.
Purpose
- To add responsibilities to objects dynamically.
- To provide a flexible alternative to subclassing for extending functionality.
Use Cases
- When you need to add functionalities to specific objects dynamically and transparently, without affecting other objects.
- When extending functionality by subclassing is impractical or results in an excessive number of subclasses.
Advantages/Disadvantages
Advantages:
- More flexibility than static inheritance.
- Avoids feature-laden classes high up in the hierarchy.
- Easy to add and remove responsibilities.
Disadvantages:
- Can lead to a large number of small classes that can become overwhelming.
- Decorators and their enclosed components are not identical to the client.
Implementation in JavaScript
class Component {
operation() {}
}
class ConcreteComponent extends Component {
operation() {
return 'ConcreteComponent';
}
}
class Decorator extends Component {
constructor(component) {
super();
this.component = component;
}
operation() {
return `Decorator(${this.component.operation()})`;
}
}
// Usage
const simple = new ConcreteComponent();
const decorated = new Decorator(simple);
console.log(simple.operation()); // "ConcreteComponent"
console.log(decorated.operation()); // "Decorator(ConcreteComponent)"
Practical Example
Suppose we’re developing a logging system in a backend application where we want to extend the behavior of a logger dynamically to add timestamps, levels, or other metadata to the log messages.
class Logger {
log(message) {
console.log(message);
}
}
class TimestampDecorator extends Logger {
constructor(logger) {
super();
this.logger = logger;
}
log(message) {
const timestampedMessage = `${new Date().toISOString()} - ${message}`;
this.logger.log(timestampedMessage);
}
}
class LevelDecorator extends Logger {
constructor(logger, level) {
super();
this.logger = logger;
this.level = level;
}
log(message) {
const leveledMessage = `[${this.level.toUpperCase()}] - ${message}`;
this.logger.log(leveledMessage);
}
}
// Usage
const simpleLogger = new Logger();
const timestampedLogger = new TimestampDecorator(simpleLogger);
const leveledLogger = new LevelDecorator(timestampedLogger, 'info');
leveledLogger.log('This message is logged with timestamp and info level.');
In this example, the TimestampDecorator
and LevelDecorator
classes add additional functionalities (timestamping and leveling) to the Logger
class dynamically. This pattern allows for flexible extension of the logger’s functionality.