Decorator
Add new functionality to objects dynamically
Overview
The Decorator pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.
Decorators wrap objects and add behavior before or after delegating to the wrapped object.
Key Concepts
Add responsibilities dynamically
Alternative to subclassing
Decorators wrap the original object
Can stack multiple decorators
Both decorator and decorated implement same interface
Code Example
1// Component interface
2interface Coffee {
3 String getDescription();
4 double getCost();
5}
6
7// Concrete component
8class SimpleCoffee implements Coffee {
9 @Override
10 public String getDescription() {
11 return "Simple Coffee";
12 }
13
14 @Override
15 public double getCost() {
16 return 2.00;
17 }
18}
19
20// Base Decorator
21abstract class CoffeeDecorator implements Coffee {
22 protected Coffee decoratedCoffee;
23
24 public CoffeeDecorator(Coffee coffee) {
25 this.decoratedCoffee = coffee;
26 }
27
28 @Override
29 public String getDescription() {
30 return decoratedCoffee.getDescription();
31 }
32
33 @Override
34 public double getCost() {
35 return decoratedCoffee.getCost();
36 }
37}
38
39// Concrete Decorators
40class MilkDecorator extends CoffeeDecorator {
41 public MilkDecorator(Coffee coffee) {
42 super(coffee);
43 }
44
45 @Override
46 public String getDescription() {
47 return decoratedCoffee.getDescription() + ", Milk";
48 }
49
50 @Override
51 public double getCost() {
52 return decoratedCoffee.getCost() + 0.50;
53 }
54}
55
56class SugarDecorator extends CoffeeDecorator {
57 public SugarDecorator(Coffee coffee) {
58 super(coffee);
59 }
60
61 @Override
62 public String getDescription() {
63 return decoratedCoffee.getDescription() + ", Sugar";
64 }
65
66 @Override
67 public double getCost() {
68 return decoratedCoffee.getCost() + 0.20;
69 }
70}
71
72class WhipCreamDecorator extends CoffeeDecorator {
73 public WhipCreamDecorator(Coffee coffee) {
74 super(coffee);
75 }
76
77 @Override
78 public String getDescription() {
79 return decoratedCoffee.getDescription() + ", Whip Cream";
80 }
81
82 @Override
83 public double getCost() {
84 return decoratedCoffee.getCost() + 0.70;
85 }
86}
87
88// Usage
89public class Main {
90 public static void main(String[] args) {
91 // Plain coffee
92 Coffee coffee = new SimpleCoffee();
93 System.out.println(coffee.getDescription() + " $" + coffee.getCost());
94
95 // Coffee with milk
96 coffee = new MilkDecorator(coffee);
97 System.out.println(coffee.getDescription() + " $" + coffee.getCost());
98
99 // Add sugar
100 coffee = new SugarDecorator(coffee);
101 System.out.println(coffee.getDescription() + " $" + coffee.getCost());
102
103 // Add whip cream
104 coffee = new WhipCreamDecorator(coffee);
105 System.out.println(coffee.getDescription() + " $" + coffee.getCost());
106 // Output: Simple Coffee, Milk, Sugar, Whip Cream $3.40
107 }
108}Each decorator wraps the coffee and adds its ingredient. They can be stacked in any order.
When to Use
Adding responsibilities dynamically
When extension by subclassing is impractical
Features that can be combined in various ways
When you need to add/remove functionality at runtime
Real-World Example
- Java I/O streams (BufferedInputStream wraps FileInputStream)
- UI components (adding scrollbars, borders)
- Text formatting (bold, italic wrappers)
- Middleware in web frameworks
💡 Interview Tips
Show coffee/pizza toppings example
Explain stacking decorators
Compare with inheritance limitations
Mention Java I/O as real example
Know difference from Proxy (adds functionality vs controls access)