π 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!
Top comments (0)