Decorator pattern
The decorator pattern wraps objects to add behavior at runtime without modifying the original class. Stack decorators like layers, each adding one responsibility while keeping the same interface.
The Problem It Solves
You're building a coffee shop order system. The base coffee costs $2, and customers can add milk, sugar, or whipped cream. Without a pattern, you subclass every combination.
Three toppings produce 8 subclasses. Five produce 32. Every new topping doubles the entire class hierarchy. Testing becomes a nightmare because each combination is a separate class with duplicated logic.
Here is what changes when you apply the Decorator pattern.
Instead of one class per combination, you create one class per behavior. Each decorator wraps any Coffee and adds exactly one thing. Three behaviors, three decorator classes, infinite combinations.
Structure
Coffee is the component interface that both the concrete component (BasicCoffee) and all decorators share. CoffeeDecorator is the abstract decorator: it holds a reference to the wrapped Coffee and forwards calls by default. Each concrete decorator (milk, sugar, whipped cream) overrides methods to add its own behavior before or after delegating to the wrapped object.
The key insight: every decorator IS-A Coffee and HAS-A Coffee. That is what enables stacking.
Implementation
I reach for this pattern when I need to mix and match behaviors without a subclass explosion. The abstract decorator does the boring forwarding work, and each concrete decorator focuses on exactly one responsibility.
One decorator, one job
Each decorator should do exactly one thing. If your MilkAndSugarDecorator exists, you've missed the point. Split it into MilkDecorator and SugarDecorator, then compose them.
// Component interface: both real coffees and decorators implement this.
// Clients only depend on this interface, never on concrete classes.
public interface Coffee {
double cost();
String description();
}Why the abstract decorator?
You could skip CoffeeDecorator and have each decorator implement Coffee directly. But then every decorator must forward every method, even the ones it doesn't change. The abstract decorator handles forwarding as the default, so concrete decorators only override the methods they care about.
Decorator ordering and composition
The order you stack decorators changes the behavior. This is powerful but can be surprising.
// Order A: format first, then rate-limit
Notifier orderA = new RateLimitDecorator(new FormattingDecorator(notifier));
// Rate limit check happens first. If allowed, formatting runs, then send.
// Order B: rate-limit first, then format
Notifier orderB = new FormattingDecorator(new RateLimitDecorator(notifier));
// Formatting runs first (wasted work), then rate limit might reject.
The general rule: put short-circuiting decorators (rate limiting, caching, auth checks) on the outside so they reject early. Put transforming decorators (formatting, compression, encryption) closer to the core where they process only accepted requests.
In Spring, you control this with @Order annotations on your decorator beans. In manual composition, it is just the nesting order in the constructor chain.
How It Works
- Client calls
cost()on the outermost decorator. The client has no idea how many layers are wrapped inside. - Each decorator delegates to its wrapped object first.
WhippedCreamDecoratorcallsSugar.cost(), which callsMilk.cost(), which callsBasicCoffee.cost(). - The base component returns its value.
BasicCoffeereturns $2.00. No delegation, this is where the recursion bottoms out. - Each decorator adds its contribution on the way back up. Milk adds $0.50, sugar adds $0.25, whipped cream adds $0.75.
- The client receives the fully composed result. $3.50, with a description of "Basic coffee, milk, sugar, whipped cream."
The beauty is that you can add a sixth topping tomorrow without touching BasicCoffee or any existing decorator. Just write a new class that extends CoffeeDecorator.
In an interview, draw the nested boxes: WhippedCream(Sugar(Milk(BasicCoffee))). Then trace a method call through the layers. That demonstrates you understand the recursive delegation structure.
Real-World Examples
- Java I/O Streams are the classic decorator example.
BufferedInputStreamwrapsFileInputStream, which wraps a file descriptor.new BufferedInputStream(new FileInputStream("data.txt"))adds buffering without modifying how files are read. Every stream IS-AInputStreamand HAS-AInputStream.
// Three decorators stacked: read file -> buffer -> decompress
InputStream in = new GZIPInputStream( // Decorator 3: decompresses
new BufferedInputStream( // Decorator 2: adds buffering
new FileInputStream("data.gz") // Concrete component: raw file
)
);
// The caller sees one InputStream. Each layer adds one behavior.
Collections.unmodifiableList()wraps anyListwith a decorator that throwsUnsupportedOperationExceptionon write methods. The original list stays mutable internally, but clients with the wrapped reference can only read.- Spring's
HttpServletRequestWrapperis an abstract decorator forHttpServletRequest. Servlet filters use it to modify request attributes (like adding authentication headers) without altering the original request object.
I reach for these when I notice the same pattern in my own code: "I want to add X to this object without opening it up." If the standard library does it this way, it is a well-tested approach.
When to Use / When NOT to Use
Use when:
- You want to follow the Single Responsibility Principle: each decorator handles one concern
- You need to add responsibilities to objects dynamically without affecting other objects of the same class
- Subclassing would create a combinatorial explosion of classes (3+ independent behaviors that can combine)
- You want behaviors to be stackable and reorderable at runtime
- The component interface is narrow enough that forwarding all methods in the abstract decorator is manageable
Skip when:
- The interface has 20+ methods, making the abstract decorator painful to write and maintain
- You only have one behavior to add and it will never change (just subclass or modify the class)
- Order of decoration must be strictly enforced (decorators don't inherently enforce ordering)
- The decorated object's identity matters (e.g.,
a == bchecks will fail through wrapper layers)
If you see combinatorial subclass explosion, you need Decorator. If you see a single fixed behavior, just add a method.
Watch for identity traps
decoratedCoffee.equals(originalCoffee) returns false unless you override equals() in your decorator chain. If your code uses reference equality or relies on instanceof checks, decorators can silently break it.
Common Mistakes in Interviews
| # | What candidates say | Why it's wrong | What to say instead |
|---|---|---|---|
| 1 | "Decorator is the same as inheritance" | Decorator uses composition, not inheritance. You can add and remove behaviors at runtime. Subclassing is fixed at compile time and creates class explosions. | "Decorator uses composition to add behavior dynamically. Inheritance is static and leads to combinatorial explosion." |
| 2 | "The decorator replaces the original object" | The decorator wraps it. The original object still exists inside, doing its job. The decorator delegates to it. | "The decorator wraps the original and delegates calls to it. The original object is preserved, not replaced." |
| 3 | "Decorator and Proxy are the same pattern" | Same structure (wrapper implementing the same interface), different intent. Decorator adds behavior. Proxy controls access. A logging decorator adds functionality; a caching proxy controls when the real object is called. | "Same UML structure, different purpose. Decorator adds behavior; Proxy controls access or lifecycle." |
| 4 | "I'd use Decorator to convert between interfaces" | That is the Adapter pattern. Decorator keeps the same interface. If the wrapper exposes a different interface than the wrapped object, it is not a decorator. | "Decorator preserves the interface. If I need to convert interfaces, that is Adapter." |
| 5 | "You always need the abstract decorator class" | Not always. If the component interface has only one or two methods, concrete decorators can implement it directly without much boilerplate. The abstract class helps when the interface is larger. | "The abstract decorator reduces boilerplate for large interfaces. For small interfaces, concrete decorators can implement it directly." |
Decorator vs Proxy vs Adapter
Use this flowchart when a wrapper pattern comes up in an interview and you need to pick the right one.
| Dimension | Decorator | Proxy | Adapter |
|---|---|---|---|
| Intent | Add behavior | Control access | Convert interface |
| Interface | Same as wrapped | Same as wrapped | Different from adaptee |
| Wrapping | Multiple layers stacked | Usually single layer | Single layer |
| Client awareness | Client knows it's decorated (chooses toppings) | Client doesn't know (transparency) | Client uses the target interface |
| Typical use | Logging, metrics, formatting, compression | Lazy loading, caching, access control | Legacy integration, third-party library wrapping |
The test: "Am I adding something new?" Then Decorator. "Am I controlling when or whether the real thing runs?" Then Proxy. "Am I translating between two incompatible interfaces?" Then Adapter.
Interview shortcut
When given a structural pattern question, draw a simple diagram: one interface, one real object, one wrapper. Then explain the intent. The structure is identical for Decorator and Proxy. What differentiates them is always the why, not the how.
Test Your Understanding
Quick Recap
- Decorator wraps an object with another object that shares the same interface. Each wrapper adds one responsibility. This is composition over inheritance in action.
- The abstract decorator handles method forwarding so concrete decorators only override what they change. Skip the abstract class only when the interface is very small (1-2 methods).
- Decorators are stackable:
Whip(Sugar(Milk(BasicCoffee))). Each layer is independent and reorderable. - Decorator adds behavior; Proxy controls access; Adapter converts interfaces. Same wrapper structure, different intent.
- Order of decoration matters. Put short-circuiting decorators (rate limiting, caching) on the outside; put transforming decorators (formatting, compression) closer to the core.
- Java I/O streams are the textbook example.
BufferedInputStream(FileInputStream)is decorator in production code you use every day. - In an interview, draw the recursive structure (each decorator wraps a component), trace a method call through the layers, and explain that adding a new behavior means adding a new class without modifying existing ones.