9. Decorator
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
- Decorator vs Proxy confusion — Decorator adds behavior; Proxy controls access (lazy load, security).
- Breaking the Liskov contract — decorated object must be substitutable for the original Component.
- Not forwarding all interface methods — partial delegation leaves some methods undecorated.
- Wrong stacking order — authentication should typically wrap logging, not the reverse.
- Creating a “god decorator” — one decorator that does logging + auth + caching violates Single Responsibility.
Related Patterns
- 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.