Home/Design Patterns/Decorator Pattern

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.

Coffee.java
java
1
2
3
4
5
// Component Interface - what all coffees must implement
public interface Coffee {
String getDescription();
double getCost();
}
SimpleCoffee.java
java
1
2
3
4
5
6
7
8
9
10
11
12
// Concrete Component - the base coffee
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 2.0; // Base price $2
}
}
CoffeeDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Abstract Decorator - wraps a Coffee object
public 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();
}
}
MilkDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Concrete Decorator - adds milk
public 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
}
}
SugarDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Concrete Decorator - adds sugar
public 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
}
}
WhippedCreamDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Concrete Decorator - adds whipped cream
public 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
}
}
CoffeeShopDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Client code - Making different coffee orders
public 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 Coffee
Cost: $2.0
Simple Coffee, Milk
Cost: $2.5
Simple Coffee, Milk, Sugar
Cost: $2.7
Simple Coffee, Milk, Sugar, Whipped Cream
Cost: $3.4
Simple Coffee, Milk, Milk
Cost: $3.0
*/

Example 2: Text Formatting (Multiple Decorations)

Applying multiple text formatting options like bold, italic, and underline to base text.

Text.java
java
1
2
3
4
// Component Interface
public interface Text {
String getContent();
}
PlainText.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
// Concrete Component - plain text
public class PlainText implements Text {
private String content;
public PlainText(String content) {
this.content = content;
}
@Override
public String getContent() {
return content;
}
}
TextDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
// Abstract Decorator
public abstract class TextDecorator implements Text {
protected Text decoratedText;
public TextDecorator(Text text) {
this.decoratedText = text;
}
@Override
public String getContent() {
return decoratedText.getContent();
}
}
BoldDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
// Concrete Decorator - makes text bold
public class BoldDecorator extends TextDecorator {
public BoldDecorator(Text text) {
super(text);
}
@Override
public String getContent() {
return "<b>" + decoratedText.getContent() + "</b>";
}
}
ItalicDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
// Concrete Decorator - makes text italic
public class ItalicDecorator extends TextDecorator {
public ItalicDecorator(Text text) {
super(text);
}
@Override
public String getContent() {
return "<i>" + decoratedText.getContent() + "</i>";
}
}
UnderlineDecorator.java
java
1
2
3
4
5
6
7
8
9
10
11
// Concrete Decorator - underlines text
public class UnderlineDecorator extends TextDecorator {
public UnderlineDecorator(Text text) {
super(text);
}
@Override
public String getContent() {
return "<u>" + decoratedText.getContent() + "</u>";
}
}
TextEditorDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Client code - Text editor with formatting
public 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 World
Bold: <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