Skip to content
SP StackPractices
intermediate

Decorator Pattern

Add new functionality to objects dynamically by wrapping them. A structural design pattern for flexible behavior extension.

Topics: design

Decorator Pattern

Overview

The Decorator Pattern is a structural design pattern that lets you attach new behaviors to objects by placing them inside wrapper objects that contain the behaviors. It provides a flexible alternative to subclassing for extending functionality.

It is widely used in I/O streams (Java), middleware pipelines (Express.js), and Python’s @decorator syntax.

When to Use

Use the Decorator Pattern when:

  • You need to add responsibilities to objects dynamically and transparently
  • Extension by subclassing is impractical or impossible (e.g., final classes)
  • You want to combine multiple behaviors in various configurations
  • You need to adhere to the Single Responsibility Principle by separating concerns
  • You want to avoid a class explosion from subclassing every possible combination

Solution

Python

from abc import ABC, abstractmethod

class Coffee(ABC):
    @abstractmethod
    def cost(self) -> float:
        pass

    @abstractmethod
    def description(self) -> str:
        pass

class SimpleCoffee(Coffee):
    def cost(self) -> float:
        return 2.0

    def description(self) -> str:
        return "Simple coffee"

class MilkDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee

    def cost(self) -> float:
        return self._coffee.cost() + 0.5

    def description(self) -> str:
        return self._coffee.description() + ", milk"

# Usage
coffee = MilkDecorator(SimpleCoffee())
print(coffee.description())  # Simple coffee, milk
print(coffee.cost())         # 2.5

JavaScript

class Coffee {
  cost() {
    return 2.0;
  }

  description() {
    return "Simple coffee";
  }
}

class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

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

  description() {
    return this.coffee.description() + ", milk";
  }
}

// Usage
const coffee = new MilkDecorator(new Coffee());
console.log(coffee.description()); // Simple coffee, milk
console.log(coffee.cost());        // 2.5

Java

interface Coffee {
    double cost();
    String description();
}

class SimpleCoffee implements Coffee {
    public double cost() { return 2.0; }
    public String description() { return "Simple coffee"; }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
}

class MilkDecorator extends CoffeeDecorator {
    MilkDecorator(Coffee coffee) { super(coffee); }
    public double cost() { return coffee.cost() + 0.5; }
    public String description() { return coffee.description() + ", milk"; }
}

// Usage
Coffee coffee = new MilkDecorator(new SimpleCoffee());
System.out.println(coffee.description()); // Simple coffee, milk
System.out.println(coffee.cost());        // 2.5

Explanation

The Decorator Pattern relies on composition over inheritance:

  • Component Interface (Coffee): Defines the contract for both concrete components and decorators
  • Concrete Component (SimpleCoffee): The base object being wrapped
  • Decorator (MilkDecorator): Implements the same interface and delegates to the wrapped object

Decorators can be nested arbitrarily. You can wrap a MilkDecorator with a SugarDecorator, then with a WhipDecorator, building behavior stacks at runtime.

Variants

VariantUse CaseTrade-off
Class-basedStrongly typed languages (Java, C#)Verbose but type-safe
Function-basedPython @decorator syntaxConcise, but less explicit composition
Middleware pipelineWeb frameworks (Express, Koa)Great for request/response processing

Best Practices

  • Keep decorators transparent: They should implement the exact same interface as the component
  • Delegate all methods: Unless intentionally overriding, pass every call to the wrapped object
  • Avoid stateful decorators when possible to reduce complexity
  • Document decorator order: Some decorators may behave differently depending on wrapping order
  • Prefer composition over inheritance: This is the core philosophy of the pattern

Common Mistakes

  • Forgetting to delegate: A decorator that does not forward calls breaks the chain
  • Leaky abstraction: Decorators exposing methods not in the component interface
  • Order sensitivity: Decorators that depend on being inner or outer can cause subtle bugs
  • Over-decoration: Too many nested decorators make debugging and profiling difficult
  • State conflicts: Multiple decorators holding conflicting state about the same component

Frequently Asked Questions

Q: What is the difference between Decorator and Proxy? A: Decorator adds responsibilities dynamically. Proxy controls access to an object (lazy initialization, access control, logging). They have similar structure but different intent.

Q: Can decorators be removed at runtime? A: Not easily in most implementations. If you need add/remove flexibility, consider the Chain of Responsibility pattern instead.

Q: Are Python’s @decorator syntax and the Decorator Pattern the same? A: Python’s @decorator is a language feature for wrapping functions. The Decorator Pattern is an OOP design pattern for wrapping objects. They share the concept but apply to different levels.