The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. Each decorator wraps a component and delegates to it while adding its own behavior.

Intent and Motivation

Intent: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extension.

Motivation: Subclassing for every combination of features explodes the class hierarchy: BufferedFileInputStream, GzipFileInputStream, BufferedGzipFileInputStream… Decorator wraps an existing object, implements the same interface, and adds behavior before or after delegating. Wrappers stack: new BufferedInputStream(new GzipInputStream(new FileInputStream("data.txt"))). Each layer is independent and combinable at runtime.

This is the foundation of middleware pipelines, I/O stream stacks, and UI enhancement layers.

Structure (UML-like)

  ┌──────────────┐
│  Component   │ (interface)
├──────────────┤
│ + operation()│
└──────▲───────┘
       │
  ┌────┴─────────────────────────────┐
  │                                  │
┌─┴────────────┐              ┌──────┴───────┐
│ConcreteComp. │              │  Decorator   │ (abstract)
├──────────────┤              ├──────────────┤
│+ operation() │              │ - component  │
└──────────────┘              │ + operation()│ (delegates + extends)
                              └──────▲───────┘
                                     │
                              ┌──────┴───────┐
                              │ConcreteDeco. │
                              │  A, B, C...  │
                              └──────────────┘
  

Participants:

  • Component — common interface for wrappers and wrapped objects.
  • ConcreteComponent — base object receiving decorations.
  • Decorator — holds a Component reference, implements the same interface.
  • ConcreteDecorator — adds specific behavior before/after delegating.

Java Example

  interface Coffee {
    String getDescription();
    double getCost();
}

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

abstract class CoffeeDecorator implements Coffee {
    protected final Coffee wrapped;
    CoffeeDecorator(Coffee coffee) { this.wrapped = coffee; }
    public String getDescription() { return wrapped.getDescription(); }
    public double getCost() { return wrapped.getCost(); }
}

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

class SugarDecorator extends CoffeeDecorator {
    SugarDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() { return wrapped.getDescription() + ", sugar"; }
    public double getCost() { return wrapped.getCost() + 0.2; }
}

// Usage — stack decorators at runtime
Coffee order = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(order.getDescription()); // Simple coffee, milk, sugar
System.out.println(order.getCost());         // 2.7
  

JavaScript Example

  // Component
class Server {
  handle(request) {
    return { status: 200, body: `Handled: ${request.path}` };
  }
}

// Decorator factory
function withLogging(server) {
  return {
    handle(request) {
      console.log(`[LOG] ${request.method} ${request.path}`);
      const response = server.handle(request);
      console.log(`[LOG] Status: ${response.status}`);
      return response;
    }
  };
}

function withAuth(server) {
  return {
    handle(request) {
      if (!request.token) {
        return { status: 401, body: 'Unauthorized' };
      }
      return server.handle(request);
    }
  };
}

function withCors(server) {
  return {
    handle(request) {
      const response = server.handle(request);
      response.headers = { 'Access-Control-Allow-Origin': '*' };
      return response;
    }
  };
}

// Stack decorators
const app = withCors(withAuth(withLogging(new Server())));
app.handle({ method: 'GET', path: '/api/users', token: 'abc123' });
  

Real-World Use Cases

Framework / System Usage
Java I/O BufferedInputStream, DataInputStream, GZIPInputStream — stacked stream decorators.
Java Collections Collections.synchronizedList(), unmodifiableList() — behavioral decorators.
Express.js middleware Each middleware decorates the request/response pipeline with logging, auth, parsing.
React HOCs withRouter(), connect() — higher-order components decorate base components.
Python @decorator Function decorators add logging, caching, retry behavior dynamically.
Spring AOP Aspects decorate bean methods with transactions, security, and logging.

Pros and Cons

Pros Cons
Extend behavior without subclassing — compose at runtime Many small decorator classes can be hard to debug
Mix and match features in any order (when designed well) Order of decorators matters — auth(logging(x)) vs logging(auth(x))
Single Responsibility — each decorator does one thing Hard to remove a specific decorator from the middle of a stack
Open/Closed Principle — add new decorators without changing existing code Component interface must support all decorators (fat interface risk)
Alternative to complex subclass hierarchies Can obscure the true type of the underlying object

When to Use vs When NOT to Use

Use when:

  • You need to add responsibilities to individual objects dynamically and transparently.
  • Extension by subclassing is impractical (combinatorial explosion of features).
  • You want to add and remove responsibilities at runtime.
  • The enriched behavior should be composable (stackable layers).

Do NOT use when:

  • A fixed set of enhancements can be handled with simple subclassing.
  • You need to change the core interface (use Adapter).
  • You need to control access to an object, not add behavior (use Proxy).
  • The object structure is a tree of part-whole relationships (use Composite).

Common Mistakes

  1. Decorator vs Proxy confusion — Decorator adds behavior; Proxy controls access (lazy load, security).
  2. Breaking the Liskov contract — decorated object must be substitutable for the original Component.
  3. Not forwarding all interface methods — partial delegation leaves some methods undecorated.
  4. Wrong stacking order — authentication should typically wrap logging, not the reverse.
  5. Creating a “god decorator” — one decorator that does logging + auth + caching violates Single Responsibility.
  • Adapter — changes interface; Decorator keeps the same interface and adds behavior.
  • Composite — structural recursion for trees; Decorator is linear wrapping chain.
  • Proxy — same structure as Decorator; intent differs (access control vs enrichment).
  • Strategy — changes the core algorithm; Decorator wraps and extends without replacing.
  • Chain of Responsibility — decorators delegate to one wrapped object; CoR passes along a chain of handlers.