Decorator Pattern
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
1What is Decorator Pattern?
Start with a basic coffee. Add milk (+$0.50). Add whipped cream (+$0.70). Add caramel (+$0.60). Each addition "decorates" the coffee with new features and cost, without changing the original coffee class.
You have a gift. Wrap it in paper (decorator). Add a ribbon (decorator). Add a bow (decorator). Each layer adds to the presentation without changing the gift itself.
Decorator Pattern Structure
2Why Not Just Use Inheritance?
Coffee ├── CoffeeWithMilk ├── CoffeeWithCream ├── CoffeeWithMilkAndCream ├── CoffeeWithMilkAndSugar ├── CoffeeWithCreamAndSugar ├── ... (16 classes!)
Coffee (component) ├── MilkDecorator ├── CreamDecorator ├── SugarDecorator └── CaramelDecorator // Mix and match!
Decorator uses composition: each decorator HAS-A component it wraps. This allows stacking decorators at runtime in any order, creating combinations that would require exponential subclasses with inheritance.
3Implementation
// ========== Component Interface ==========
interface Coffee {
String getDescription();
double getCost();
}
// ========== Concrete Component ==========
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double getCost() {
return 2.00;
}
}
// ========== Base Decorator ==========
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee; // Wrapped component
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double getCost() {
return coffee.getCost();
}
}
// ========== Concrete Decorators ==========
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double getCost() {
return coffee.getCost() + 0.50;
}
}
class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Whipped Cream";
}
@Override
public double getCost() {
return coffee.getCost() + 0.70;
}
}
class CaramelDecorator extends CoffeeDecorator {
public CaramelDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Caramel";
}
@Override
public double getCost() {
return coffee.getCost() + 0.60;
}
}
// ========== Usage ==========
public class Main {
public static void main(String[] args) {
// Simple coffee
Coffee order1 = new SimpleCoffee();
System.out.println(order1.getDescription() + " = $" + order1.getCost());
// Coffee with milk
Coffee order2 = new MilkDecorator(new SimpleCoffee());
System.out.println(order2.getDescription() + " = $" + order2.getCost());
// Fancy coffee with multiple decorators
Coffee order3 = new CaramelDecorator(
new WhippedCreamDecorator(
new MilkDecorator(
new SimpleCoffee())));
System.out.println(order3.getDescription() + " = $" + order3.getCost());
}
}Simple Coffee = $2.00 Simple Coffee, Milk = $2.50 Simple Coffee, Milk, Whipped Cream, Caramel = $3.80
4Real-World Applications
5Interview Follow-up Questions
Interview Follow-up Questions
Common follow-up questions interviewers ask