← Back to Home

Decorator Pattern

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

What is it?

The Decorator pattern lets you wrap objects with new behavior at runtime without modifying the original class.

Example

interface Coffee {
  cost: () => number;
  description: () => string;
}

class BasicCoffee implements Coffee {
  cost() {
    return 5;
  }
  description() {
    return 'Basic Coffee';
  }
}

class MilkDecorator implements Coffee {
  constructor(private coffee: Coffee) {}

  cost() {
    return this.coffee.cost() + 2;
  }

  description() {
    return this.coffee.description() + ', Milk';
  }
}

class SugarDecorator implements Coffee {
  constructor(private coffee: Coffee) {}

  cost() {
    return this.coffee.cost() + 1;
  }

  description() {
    return this.coffee.description() + ', Sugar';
  }
}

const coffee = new SugarDecorator(new MilkDecorator(new BasicCoffee()));
console.log(coffee.description()); // "Basic Coffee, Milk, Sugar"
console.log(coffee.cost()); // 8

Common Uses

  • Adding features to components dynamically
  • UI components (e.g., wrappers around buttons, inputs)
  • Middleware chains (e.g., Express, Redux)

When to Use

  • You want to add responsibilities to objects without subclassing
  • Need to mix and match behaviors at runtime

Caution

  • Too many small decorator classes can complicate maintenance
  • Harder to trace functionality than with inheritance