Back to Overview

Decorator Pattern

The Decorator Pattern allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.

This pattern is particularly useful when you want to add responsibilities to objects without modifying their underlying code, following the Open/Closed Principle.

Problem

Imagine you're building a coffee ordering system. You have a basic coffee, but customers can customize it with various add-ons like milk, sugar, whipped cream, or chocolate. Creating a separate class for each combination would lead to an explosion of classes. How can you add these features dynamically without creating a huge class hierarchy?

Component ConcreteComponent Decorator ConcreteDecoratorA ConcreteDecoratorB component BasicCoffee MilkDecorator (BasicCoffee) WhipCreamDecorator (MilkDecorator(BasicCoffee))

Components

Implementation Example

Let's implement a coffee ordering system using the Decorator pattern:

// Component Interface
class Coffee {
    getCost() {
        // To be implemented by concrete components
    }
    
    getDescription() {
        // To be implemented by concrete components
    }
}

// Concrete Component
class SimpleCoffee extends Coffee {
    getCost() {
        return 5;
    }
    
    getDescription() {
        return "Simple Coffee";
    }
}

// Base Decorator
class CoffeeDecorator extends Coffee {
    constructor(decoratedCoffee) {
        super();
        this.decoratedCoffee = decoratedCoffee;
    }
    
    getCost() {
        return this.decoratedCoffee.getCost();
    }
    
    getDescription() {
        return this.decoratedCoffee.getDescription();
    }
}

// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
    constructor(decoratedCoffee) {
        super(decoratedCoffee);
    }
    
    getCost() {
        return this.decoratedCoffee.getCost() + 1;
    }
    
    getDescription() {
        return this.decoratedCoffee.getDescription() + ", with milk";
    }
}

class WhipDecorator extends CoffeeDecorator {
    constructor(decoratedCoffee) {
        super(decoratedCoffee);
    }
    
    getCost() {
        return this.decoratedCoffee.getCost() + 2;
    }
    
    getDescription() {
        return this.decoratedCoffee.getDescription() + ", with whipped cream";
    }
}

class ChocolateDecorator extends CoffeeDecorator {
    constructor(decoratedCoffee) {
        super(decoratedCoffee);
    }
    
    getCost() {
        return this.decoratedCoffee.getCost() + 1.5;
    }
    
    getDescription() {
        return this.decoratedCoffee.getDescription() + ", with chocolate";
    }
}

// Usage
let myCoffee = new SimpleCoffee();
console.log(myCoffee.getDescription() + ": $" + myCoffee.getCost());

Interactive Demo: Coffee Customizer

Build your own coffee by adding decorators! Select different options to see how the Decorator pattern works in action.

// Coffee details will appear here

When to Use

Benefits

Real-World Uses