Introduction
The Decorator Pattern is a structural design pattern that lets you attach new responsibilities to an object dynamically without altering its structure. It provides a flexible alternative to subclassing for extending functionality.
Why Decorator Pattern Matters
Imagine you have a TextView
component and want to add scrolling, border, and highlighting. Instead of creating dozens of subclasses like ScrollTextViewWithBorder
, you can wrap behaviors using decorators at runtime.
This makes the Decorator Pattern a great choice for flexible, extensible design.
Core Intent and Participants
- Intent: Attach additional responsibilities to an object dynamically without modifying its code. Decorators provide a flexible alternative to subclassing.
Participants
Component
: Defines the interface for objects that can have added behavior.ConcreteComponent
: Implements the base behavior.Decorator
: Implements the same interface and wraps a component.ConcreteDecorator
: Adds specific behavior.
UML Diagram (Text)
+------------------+
| Component |
+------------------+
| + operation() |
+------------------+
^
|
+------------------+ +----------------------+
| ConcreteComponent| | Decorator |
+------------------+ +----------------------+
| + operation() | ----> | - component:Component |
| + operation() |
+----------------------+
^
|
+---------------------------+
| ConcreteDecoratorA |
| ConcreteDecoratorB |
+---------------------------+
Real-World Use Cases
- Java I/O Streams (
BufferedReader
,DataInputStream
) - UI elements with dynamic styling
- Logging enhancements (add timestamp, formatters)
- Encryption/wrapping of data packets
Java Implementation Strategy
Example: Coffee Shop – Add Milk, Sugar, and Whipped Cream
Step 1: Component Interface
public interface Coffee {
String getDescription();
double cost();
}
Step 2: Concrete Component
public class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double cost() {
return 5.0;
}
}
Step 3: Decorator
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
}
Step 4: Concrete Decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", Milk";
}
public double cost() {
return decoratedCoffee.cost() + 1.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", Sugar";
}
public double cost() {
return decoratedCoffee.cost() + 0.5;
}
}
Step 5: Client Code
public class DecoratorDemo {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println("Cost: $" + coffee.cost()); // Cost: $7.0
}
}
✅ You can keep stacking decorators without changing the base class.
Pros and Cons
✅ Pros
- Adheres to Open/Closed Principle (open for extension, closed for modification)
- Avoids class explosion caused by inheritance
- Combines behaviors dynamically at runtime
❌ Cons
- Many small classes to manage
- Debugging can be harder due to multiple layers of wrapping
Anti-Patterns and Misuse
- Using decorators when subclassing would be simpler
- Over-decorating objects leading to performance issues
- Misnaming decorators (hard to distinguish behavior chain)
Decorator vs Adapter vs Proxy
Pattern | Goal | Modifies Interface? | Real-World Analogy |
---|---|---|---|
Decorator | Add behavior dynamically | ❌ No | Toppings on a pizza |
Adapter | Convert one interface to another | ✅ Yes | Travel power plug adapter |
Proxy | Control access to an object | ❌ No | Bank ATM as proxy to bank |
Refactoring Legacy Code
Before
public class LatteWithSugarAndCream extends Latte {
public String getDescription() { return "Latte, Sugar, Cream"; }
public double cost() { return 8.0; }
}
After (Using Decorator)
Coffee coffee = new Latte();
coffee = new SugarDecorator(new CreamDecorator(coffee));
✅ Eliminates the need for subclass explosion.
Best Practices
- Keep decorators interchangeable by maintaining consistent interfaces
- Compose instead of inherit for dynamic behavior
- Name decorators clearly to reflect added behavior
- Avoid nesting too deep—keep it readable
Real-World Analogy
Think of ordering a burger. You start with a base patty and add toppings: cheese, lettuce, tomato, sauces. You don’t need a separate class for every combination; toppings (decorators) add behavior on top of the base.
Java Version Relevance
- Java 8+: Use lambdas for functional-style decorators
- Java I/O Streams: Classic use-case of decorators (
InputStream
,BufferedInputStream
)
Conclusion & Key Takeaways
- Decorator Pattern adds functionality at runtime without modifying existing code.
- Ideal for building flexible and extensible object behaviors.
- Prefer composition over inheritance where behavior varies.
- Use it in UI, logging, I/O streams, and formatting pipelines.
FAQ – Decorator Pattern in Java
1. What is the Decorator Pattern?
A structural design pattern to add behavior to objects dynamically.
2. When should I use it?
When you need to enhance object behavior without changing their class.
3. What’s the real Java example of this pattern?
Java I/O (BufferedReader
, DataOutputStream
)
4. Does it replace inheritance?
Yes, it avoids deep inheritance trees.
5. Can decorators be stacked?
Yes, decorators can be nested as much as needed.
6. Is it test-friendly?
Yes. You can test each decorator independently.
7. What if I need to remove a decorator?
You’ll need to manage composition manually—there’s no automatic removal.
8. Can I use it with Spring Beans?
Yes, by wrapping beans at runtime or using @Primary
and profiles.
9. Does this pattern impact performance?
Slightly. Too many wrappers can add overhead.
10. Is this the same as middleware?
Middleware chains often use the same principles, so yes, conceptually similar.