The Decorator Design Pattern: Dynamic Superpowers
Master the Decorator Pattern in Java. Learn how to add features to objects dynamically without inheritance explosions. The ultimate 'Wrapper' pattern.
Moshiour Rahman
The Problem: Class Explosion
Imagine you run a Coffee Shop. You have a class Beverage.
class Beverage { ... }
class DarkRoast extends Beverage { ... }
class Espresso extends Beverage { ... }
Now customers want Toppings: Milk, Mocha, Soy, Whip.
If you use inheritance, you end up with a nightmare:
DarkRoastWithMilkDarkRoastWithMilkAndMochaEspressoWithSoyAndWhip- …
This is the “Class Explosion” anti-pattern. You cannot create a class for every possible combination.
The Solution: The Decorator Pattern
The Decorator Pattern allows you to attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
Real-Life Analogy: Ordering Coffee ☕
You don’t buy a pre-manufactured “Soy Mocha Espresso”.
- You take a cup.
- You add Espresso (Base Component).
- You wrap it with Mocha (Decorator).
- You wrap it with Soy (Decorator).
- You wrap it with Whip (Decorator).
The final object is a big ball of wrappers, but it still looks like a Beverage.
Visualizing the Pattern

Implementation
1. The Component Interface
public interface Beverage {
String getDescription();
double cost();
}
2. Concrete Component (The Base)
public class Espresso implements Beverage {
@Override
public String getDescription() {
return "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
3. The Decorator (The Wrapper)
This is the magic part. It is a Beverage, but it also has a Beverage.
public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage; // The object we are wrapping
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
4. Concrete Decorators
public class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Milk";
}
@Override
public double cost() {
return beverage.cost() + 0.50; // Delegate + add own cost
}
}
public class Mocha extends CondimentDecorator {
public Mocha(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public double cost() {
return beverage.cost() + 0.99;
}
}
Usage
// 1. Order an Espresso
Beverage myDrink = new Espresso();
// 2. Add Milk
myDrink = new Milk(myDrink);
// 3. Add Mocha
myDrink = new Mocha(myDrink);
// 4. Calculate final cost
System.out.println(myDrink.getDescription()); // "Espresso, Milk, Mocha"
System.out.println(myDrink.cost()); // 1.99 + 0.50 + 0.99
In The Wild (Real World Examples)
1. java.io Package
The most famous example. The I/O library is purely built on decorators.
// FileInputStream is the base Component
// BufferedInputStream is a Decorator (adds buffering)
// GZIPInputStream is a Decorator (adds compression)
InputStream in = new GZIPInputStream(
new BufferedInputStream(
new FileInputStream("test.txt")
)
);
2. UI Frameworks (Swing/JavaFX)
Adding borders or scrollbars to a window often involves wrapping the Window object in a BorderDecorator or ScrollDecorator.
Cheat Sheet
| Feature | Details |
|---|---|
| Category | Structural |
| Problem Solved | Adding responsibilities to objects dynamically without inheritance |
| Key implementation | class Decorator implements Component { Component wrapped; } |
| Pros | Flexible (add remove features at runtime), Composability |
| Cons | Can result in many small objects and “wrapper hell” (hard to debug) |
Tips to Remember 🧠
- “Matryoshka Doll”: Decorator is like Russian nesting dolls. Each doll fits inside another.
- “Wrapper”: Like Adapter, it’s a wrapper. But Adapter changes interface, Decorator adds behavior.
- Recursive: Calls usually go
this.wrapped.method()recursively until the base component is hit.
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
The Bridge Design Pattern: Decouple Abstraction from Implementation
Master the Bridge Pattern in Java with practical examples. Learn how to avoid class explosion by separating abstraction from implementation hierarchies.
JavaThe Visitor Design Pattern: Add Operations Without Modifying Classes
Master the Visitor Pattern in Java with practical examples. Add new operations to object structures without modifying existing classes using double dispatch.
JavaThe Observer Design Pattern: Don't Call Us, We'll Call You
Master the Observer Pattern in Java. Learn how to implement event-driven architectures and decouple data sources from listeners.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.