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
Advertisement
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.
Advertisement
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. Learn how to avoid class explosion by separating abstraction and implementation hierarchies.
JavaThe Visitor Design Pattern: Add Operations Without Modifying Classes
Master the Visitor Pattern in Java. Learn how to add new operations to object structures 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.