🎨 Decorator Design Pattern: Add Behavior Without Changing Code
In the world of scalable and maintainable software, modifying existing code to add new functionality can introduce bugs, break behavior, or violate open/closed principles. That’s where the Decorator Design Pattern shines.
🔍 What Is the Decorator Pattern?
The Decorator Pattern is a Structural Design Pattern that allows you to dynamically attach new responsibilities or behavior to an object without altering its structure.
Instead of subclassing to add behavior, you wrap objects in other decorator objects that extend functionality.
🧠 Real-World Analogy
Think of a coffee order ☕.
Similarly in software:
🧑💻 Real-World Use Cases in Software
// Base.java
package LLD.DecoratorDesign;
interface Coffee{
double getCost();
String getDetail();
}
// Base class on which we add responsibilty dynamically in runtime
class BaseCoffee implements Coffee{
public double getCost(){
return 10.0;
}
public String getDetail(){
return "Base Coffee";
}
}
// Decorator.java --> Abstract code of Decorator
package LLD.DecoratorDesign;
public abstract class Decorator implements Coffee{
private Coffee decoratedCoffee;
Decorator(Coffee coffee){
this.decoratedCoffee=coffee;
}
public double getCost(){
return decoratedCoffee.getCost();
}
public String getDetail(){
return decoratedCoffee.getDetail();
}
}
// Implementation of Decorator
class CoffeeWithMilk extends Decorator{
CoffeeWithMilk(Coffee coffee){
super(coffee);
}
public double getCost(){
return super.getCost()+2.0;
}
public String getDetail(){
return super.getDetail()+" with Milk";
}
}
class CoffeeWithCoco extends Decorator{
CoffeeWithCoco(Coffee coffee){
super(coffee);
}
public double getCost(){
return super.getCost()+4.0;
}
public String getDetail(){
return super.getDetail()+" with Coco";
}
}
// Main.java
package LLD.DecoratorDesign;
public class Main{
public static void main(String[] args){
System.out.println("Hello World from Main.java");
Coffee simplecoffee=new BaseCoffee();
Coffee cappuccino=new CoffeeWithMilk(simplecoffee);
System.out.println(cappuccino.getDetail());
System.out.println(cappuccino.getCost());
/*
Below we wrap the cappuccino with coco features
when we create hotchoco obj by passing cappuccino it sets the
decoratorCoffee (i.e the parent hotchoco as cappuccino) which is dynamic and at any point we can add hotchoco feature to any other
coffee by passing that coffee to hotchoco constructor
*/
Coffee hotChoco=new CoffeeWithCoco(cappuccino);
System.out.println(hotChoco.getDetail());
System.out.println(hotChoco.getCost());
}
}
🧰 When to Use the Decorator Pattern
✅ Benefits
⚠️ When Not to Use
🧩 Key Takeaway
The Decorator Pattern isn’t just about adding “extras.” It’s about writing extensible and maintainable code—allowing your software to evolve without breaking its core.
Great article, at first I thought why not inheritance, but certainly you can’t know everything on day one in software. It gives great power to a system designer to implement behaviour on the go.