Decorator

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

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.

javascript
	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.