What is decorator design pattern?
The Decorator Pattern is a structural design pattern that allows you to add new functionality to an existing object dynamically, without altering its structure. It’s a flexible alternative to subclassing.
Think of it as wrapping an object to enhance or modify its behavior.
Real-Life Example
Imagine a coffee shop:
- A basic coffee costs ₹50.
- You can add milk, sugar, or whipped cream as extra toppings.
- Each topping adds to the cost without changing the base coffee class.
Java Implementation
Create a Coffee
interface
public interface Coffee {
double getCost();
String getDescription();
}
Implement a basic coffee
public class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 50.0;
}
@Override
public String getDescription() {
return "Simple Coffee";
}
}
Create an abstract decorator
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public double getCost() {
return decoratedCoffee.getCost();
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
Add concrete decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 20.0;
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 10.0;
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar";
}
}
Client class: Using the decorators
public class Main {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " ₹ " + coffee.getCost());
// Add Milk
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " ₹ " + coffee.getCost());
// Add Sugar
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " ₹ " + coffee.getCost());
}
}
Output
Simple Coffee ₹ 50.0
Simple Coffee, Milk ₹ 70.0
Simple Coffee, Milk, Sugar ₹ 80.0
Key Takeaways
- Decorator allows behavior to be added dynamically.
- Open/Closed Principle: You can extend functionality without modifying existing code.
- Avoid creating complex inheritance trees for every combination of features.
Pros:
- More flexible than static inheritance.
- Can combine multiple decorators at runtime.
- Encourages composition over inheritance.
Cons:
- Can become complex with many decorators.
- Debugging may be harder due to nested wrappers.
When to Use Decorator Pattern
- When you want to add responsibilities to objects dynamically.
- When subclassing would result in too many classes.
- When you want to avoid modifying the original object’s code.
Summary
The Decorator Pattern is perfect when you need flexible, runtime-added behavior for objects. In our coffee example, toppings could be added in any order without touching the base coffee class.
This is Part 8 of the Java Design Patterns Series.
If you find it insightful, please share your feedback. Also let me know if you have used decorator pattern in your projects.
Next Up: Composite Design Patterns!
Top comments (0)