🔍 What is the Decorator Pattern?
The Decorator Pattern is a structural design pattern that allows behavior to be added to individual objects, either statically or dynamically, without altering the structure of the original class.
✅ When Should You Use It?
- When you want to add responsibilities to objects dynamically at runtime.
- When subclassing would lead to too many classes.
- To follow Open-Closed Principle: classes should be open for extension but closed for modification.
🧠 Real-World Analogy
Imagine you order a coffee ☕. You start with a base coffee, and then decorate it with milk, sugar, mocha, or whipped cream — all dynamically layered, without modifying the base coffee class.
🧱 Structure
+-------------------+
| Component | <-- Interface
+-------------------+
^
|
+------------------------+
| ConcreteComponent | <-- Base Implementation
+------------------------+
^
|
+------------------------+
| Decorator | <-- Abstract Wrapper
+------------------------+
^
------------------------
| | |
MilkDecorator SugarDecorator etc. <-- Concrete Decorators
☕ Example: Coffee Shop
✅ 1. Component Interface
public interface Coffee {
String getDescription();
double cost();
}
✅ 2. ConcreteComponent
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 5.0;
}
}
✅ 3. Abstract Decorator
public abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
public String getDescription() {
return decoratedCoffee.getDescription();
}
public double cost() {
return decoratedCoffee.cost();
}
}
✅ 4. Concrete Decorators
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return super.getDescription() + ", Milk";
}
public double cost() {
return super.cost() + 1.5;
}
}
public class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return super.getDescription() + ", Sugar";
}
public double cost() {
return super.cost() + 0.5;
}
}
public class MochaDecorator extends CoffeeDecorator {
public MochaDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return super.getDescription() + ", Mocha";
}
public double cost() {
return super.cost() + 2.0;
}
}
💻 Client Code
public class DecoratorDemo {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.cost());
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new MochaDecorator(coffee);
System.out.println(coffee.getDescription() + " $" + coffee.cost());
}
}
🧪 Output
Simple Coffee $5.0
Simple Coffee, Milk, Sugar, Mocha $9.0
🎯 Benefits
✅ Follows Open-Closed Principle
✅ Runtime behavior change
✅ Composable and reusable wrappers
✅ Cleaner than multiple subclasses
🧑🏫 Key Takeaways
- The Decorator Pattern is perfect when you want to extend behavior without modifying classes.
- It helps you avoid creating too many subclasses.
- You can keep adding layers of decorators to wrap original functionality.
🧩 Java Libraries Using Decorator
-
BufferedReader
,BufferedInputStream
,PrintWriter
injava.io
🎯
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
🚀 Up Next for Day 5: Want to go with Strategy, Adapter, State, or Chain of Responsibility?
Let me know your pick and I’ll deliver another deep-dive, code-rich, real-world blog for the next one!