π 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)