Decorator: Wrapping With New Responsibilities
The Problem It Solves
A coffee ordering system needs espresso + milk + caramel + double-shot combinations. Subclass-per-combination explodes combinatorially (EspressoWithMilk, EspressoWithMilkAndCaramel, …). Cross-cutting needs show the same shape elsewhere: a repository that’s cached or audited or both, an HTTP client with retries and metrics and either order. Decorator solves it by wrapping objects in same-interface layers, each adding one behavior before/after delegating.
┌────────────────────┐
Coffee ◁─────────┤ «interface» Coffee │ cost() + name()
└─────────△──────────┘
│ implements (each layer)
┌───────────────┼────────────────┐
Espresso MilkDecorator CaramelDecorator
cost(): 200 cost(): wrap.cost()+30 cost(): wrap.cost()+50
▲ each decorator HOLDS a Coffee and ADDS to it
runtime stacking:
new Caramel(new Milk(new Espresso())) → 200+30+50 = 280
new Milk(new Caramel(new Espresso())) → same total, different layering semantics
The wrapper shares the wrapped object’s interface, so clients cannot tell how many layers exist — transparency is the point.
Mechanics
interface Coffee { int cost(); String name(); }
class Espresso implements Coffee {
public int cost() { return 200; }
public String name() { return "Espresso"; }
}
abstract class CoffeeDecorator implements Coffee { // shared delegation plumbing
protected final Coffee inner;
CoffeeDecorator(Coffee inner) { this.inner = inner; }
}
class Milk extends CoffeeDecorator {
Milk(Coffee c) { super(c); }
public int cost() { return inner.cost() + 30; } // add + delegate
public String name() { return inner.name() + " + milk"; }
}
The JDK’s Most Famous Chain
new BufferedReader(
new InputStreamReader( // bytes → chars
new FileInputStream(path), UTF_8), // file → bytes
8192); // + buffering
All three implement Reader; each adds one responsibility (decoding, buffering) around the same core interface. This chain is the canonical citation for the pattern.
Production Uses
Collections.unmodifiableList(...),synchronizedList(...),checkedList(...)— behavior wrappers over any List.- Logging/timing/caching wrappers around repositories in layered backends.
- Servlet
HttpServletRequestWrapperfor request mutation in filters.
Decorator vs Proxy vs Adapter vs Strategy
| Pattern | Same interface? | Purpose |
|---|---|---|
| Decorator | Yes | Add responsibilities; stackable |
| Proxy | Yes | Control access (lazy/authz/remote); usually not stacked |
| Adapter | No — converts | Interface translation |
| Strategy | n/a | Replace algorithm wholesale |
Decorator vs proxy is the classic confusion: intent separates them (enrichment vs access control).
Trade-offs & Pitfalls
- Gains: open-closed extension; runtime composition; combinatorial freedom without class explosion.
- Costs: identity confusion — a decorated object fails
==/instanceofchecks against the concrete type; code relying on concrete internals breaks. - Stack-order sensitivity: retry-outside-metrics vs metrics-outside-retry count different things; document intended layering.
- Debugging: stack traces grow one frame per layer — deep stacks are normal here, not bugs.
Interview Framing
- “Add caching AND logging to this service” answered by composing two decorators scores fully; subclassing
CachedLoggingRepositoryloses the runtime-composition point.
Premium Content
Unlock Decorator and all premium lessons with a subscription.
From ₹199.99/year — See plans