Decorator Pattern
🍕 Explain Like I'm 5...
Imagine you're making a pizza! You start with a plain pizza (just dough and sauce), and then you can add toppings one by one!
🍕 Starting Simple:
- • You have a plain cheese pizza
- • Some people want pepperoni
- • Some people want mushrooms
- • Some people want BOTH pepperoni AND mushrooms!
- • How do you handle ALL possible topping combinations? 🤔
💡 The Solution - Decorate Your Pizza!
- • Start with plain pizza 🍕
- • Want pepperoni? WRAP it with pepperoni decorator! 🍕→🥓
- • Want mushrooms too? WRAP it again with mushroom decorator! 🥓→🍄
- • Each wrapper ADDS something new without changing what's inside!
- • You can add as many toppings as you want, in any order! 🎉
🌟 The Key Idea:
Decorators are like gift wrapping! Each layer adds something new while keeping everything inside the same. You can add one wrapper, two wrappers, or ten wrappers - and you can unwrap them to see what's inside! 🎁
🚀 Why Is This Pattern Useful?
- ✨Add new features to objects WITHOUT changing their code!
- ✨Mix and match features in any combination you want!
- ✨Add or remove features at runtime (while your program is running)!
- ✨Follow the Open/Closed Principle - open for extension, closed for modification!
📋 Pattern Purpose
The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. Instead of creating many subclasses for each combination of features, you wrap objects with decorator objects that add new behaviors.
⚡ When to Use Decorator Pattern
- ✓You want to add responsibilities to individual objects dynamically and transparently
- ✓You want to add features that can be withdrawn or combined
- ✓Extension by subclassing is impractical (would create too many subclasses)
- ✓You need to add features to objects without affecting other objects of the same class
🔧 How It Works
- 1.Component: The base interface that both original objects and decorators implement
- 2.ConcreteComponent: The original object you want to add features to
- 3.Decorator: Abstract decorator that wraps a component
- 4.ConcreteDecorator: Specific decorators that add new behaviors
💻 Java Implementations
Example 1: Coffee Shop (Beverages with Add-ons)
Making different coffee drinks by adding milk, sugar, and whipped cream to base coffee.
// Component Interface - what all coffees must implementpublic interface Coffee { String getDescription(); double getCost();}// Concrete Component - the base coffeepublic class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple Coffee"; } @Override public double getCost() { return 2.0; // Base price $2 }}// Abstract Decorator - wraps a Coffee objectpublic abstract class CoffeeDecorator implements Coffee { protected Coffee decoratedCoffee; public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; } @Override public String getDescription() { return decoratedCoffee.getDescription(); } @Override public double getCost() { return decoratedCoffee.getCost(); }}// Concrete Decorator - adds milkpublic class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Milk"; } @Override public double getCost() { return decoratedCoffee.getCost() + 0.5; // Milk costs $0.50 }}// Concrete Decorator - adds sugarpublic class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Sugar"; } @Override public double getCost() { return decoratedCoffee.getCost() + 0.2; // Sugar costs $0.20 }}// Concrete Decorator - adds whipped creampublic class WhippedCreamDecorator extends CoffeeDecorator { public WhippedCreamDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return decoratedCoffee.getDescription() + ", Whipped Cream"; } @Override public double getCost() { return decoratedCoffee.getCost() + 0.7; // Whipped cream costs $0.70 }}// Client code - Making different coffee orderspublic class CoffeeShopDemo { public static void main(String[] args) { // Order 1: Simple coffee Coffee coffee1 = new SimpleCoffee(); System.out.println(coffee1.getDescription()); System.out.println("Cost: $" + coffee1.getCost()); System.out.println(); // Order 2: Coffee with milk Coffee coffee2 = new SimpleCoffee(); coffee2 = new MilkDecorator(coffee2); System.out.println(coffee2.getDescription()); System.out.println("Cost: $" + coffee2.getCost()); System.out.println(); // Order 3: Coffee with milk and sugar Coffee coffee3 = new SimpleCoffee(); coffee3 = new MilkDecorator(coffee3); coffee3 = new SugarDecorator(coffee3); System.out.println(coffee3.getDescription()); System.out.println("Cost: $" + coffee3.getCost()); System.out.println(); // Order 4: Fancy coffee with everything! Coffee coffee4 = new SimpleCoffee(); coffee4 = new MilkDecorator(coffee4); coffee4 = new SugarDecorator(coffee4); coffee4 = new WhippedCreamDecorator(coffee4); System.out.println(coffee4.getDescription()); System.out.println("Cost: $" + coffee4.getCost()); System.out.println(); // Order 5: Double milk coffee (you can add same decorator twice!) Coffee coffee5 = new SimpleCoffee(); coffee5 = new MilkDecorator(coffee5); coffee5 = new MilkDecorator(coffee5); // Extra milk! System.out.println(coffee5.getDescription()); System.out.println("Cost: $" + coffee5.getCost()); }}/* Output:Simple CoffeeCost: $2.0Simple Coffee, MilkCost: $2.5Simple Coffee, Milk, SugarCost: $2.7Simple Coffee, Milk, Sugar, Whipped CreamCost: $3.4Simple Coffee, Milk, MilkCost: $3.0*/Example 2: Text Formatting (Multiple Decorations)
Applying multiple text formatting options like bold, italic, and underline to base text.
// Component Interfacepublic interface Text { String getContent();}// Concrete Component - plain textpublic class PlainText implements Text { private String content; public PlainText(String content) { this.content = content; } @Override public String getContent() { return content; }}// Abstract Decoratorpublic abstract class TextDecorator implements Text { protected Text decoratedText; public TextDecorator(Text text) { this.decoratedText = text; } @Override public String getContent() { return decoratedText.getContent(); }}// Concrete Decorator - makes text boldpublic class BoldDecorator extends TextDecorator { public BoldDecorator(Text text) { super(text); } @Override public String getContent() { return "<b>" + decoratedText.getContent() + "</b>"; }}// Concrete Decorator - makes text italicpublic class ItalicDecorator extends TextDecorator { public ItalicDecorator(Text text) { super(text); } @Override public String getContent() { return "<i>" + decoratedText.getContent() + "</i>"; }}// Concrete Decorator - underlines textpublic class UnderlineDecorator extends TextDecorator { public UnderlineDecorator(Text text) { super(text); } @Override public String getContent() { return "<u>" + decoratedText.getContent() + "</u>"; }}// Client code - Text editor with formattingpublic class TextEditorDemo { public static void main(String[] args) { // Plain text Text text1 = new PlainText("Hello World"); System.out.println("Plain: " + text1.getContent()); // Bold text Text text2 = new PlainText("Hello World"); text2 = new BoldDecorator(text2); System.out.println("Bold: " + text2.getContent()); // Italic text Text text3 = new PlainText("Hello World"); text3 = new ItalicDecorator(text3); System.out.println("Italic: " + text3.getContent()); // Bold + Italic Text text4 = new PlainText("Hello World"); text4 = new BoldDecorator(text4); text4 = new ItalicDecorator(text4); System.out.println("Bold + Italic: " + text4.getContent()); // Bold + Italic + Underline (all three!) Text text5 = new PlainText("Hello World"); text5 = new BoldDecorator(text5); text5 = new ItalicDecorator(text5); text5 = new UnderlineDecorator(text5); System.out.println("Bold + Italic + Underline: " + text5.getContent()); // Different order (Italic first, then Bold) Text text6 = new PlainText("Hello World"); text6 = new ItalicDecorator(text6); text6 = new BoldDecorator(text6); System.out.println("Italic first, then Bold: " + text6.getContent()); }}/* Output:Plain: Hello WorldBold: <b>Hello World</b>Italic: <i>Hello World</i>Bold + Italic: <i><b>Hello World</b></i>Bold + Italic + Underline: <u><i><b>Hello World</b></i></u>Italic first, then Bold: <b><i>Hello World</i></b>*/🌍 Real-World Examples
- 📚Java I/O Streams: BufferedReader wraps FileReader wraps InputStreamReader
- 🖼️GUI Components: Adding scrollbars, borders to windows
- 🍕Pizza/Coffee Orders: Adding toppings/ingredients
- 📝Text Editors: Bold, italic, underline formatting
- 🌐HTTP Middleware: Adding authentication, logging, compression layers
- 💾Caching: Adding caching behavior to data access objects
✅ Benefits
- ✅More Flexible than Inheritance: Can combine features in any way
- ✅Single Responsibility: Each decorator handles one feature
- ✅Runtime Flexibility: Add/remove features while program is running
- ✅Open/Closed Principle: Extend functionality without modifying existing code
- ✅Avoids Feature-Laden Classes: Don't need one class with all possible features
⚠️ Drawbacks
- ⚠️Many Small Objects: Can create lots of small decorator objects
- ⚠️Complexity: Can be hard to understand layers of wrapping
- ⚠️Order Matters: Sometimes the order of decorators affects behavior
- ⚠️Debugging Difficulty: Stack traces can be confusing with many layers
- ⚠️Identity Problems: Decorated object is not identical to original
🔑 Key Points to Remember
- 1️⃣Decorator ADDS behavior, Adapter CHANGES interface
- 2️⃣Decorators wrap objects of the same type (same interface)
- 3️⃣Can stack multiple decorators (like Russian nesting dolls)
- 4️⃣Each decorator adds ONE specific responsibility
- 5️⃣Alternative to creating many subclasses for feature combinations
- 6️⃣Java I/O streams are the most famous example of Decorator pattern
💪 Practice Scenarios
- • Create a notification system with email, SMS, and Slack decorators that can be combined
- • Build a data stream with compression, encryption, and logging decorators
- • Implement a car configurator with color, wheels, spoiler, and sunroof decorators
- • Design a photo filter system where multiple filters can be applied in sequence
- • Create a tax calculator where different tax types can be stacked