Structural Pattern

Decorator Pattern

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

12 min readHigh Priority

1What is Decorator Pattern?

Real-World Analogy
Coffee Shop

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.

Gift Wrapping

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 is a structural pattern that lets you dynamically add behaviors to objects by placing them inside special wrapper objects. It usescomposition over inheritance to extend functionality at runtime.

Decorator Pattern Structure

<<interface>>ComponentConcreteComponentDecorator- componentDecoratorADecoratorB
Decorator wraps Component and adds behavior. Multiple decorators can be stacked.

2Why Not Just Use Inheritance?

Inheritance Explosion
For a coffee with 4 optional add-ons, you need 2^4 = 16 subclasses!
Coffee
├── CoffeeWithMilk
├── CoffeeWithCream
├── CoffeeWithMilkAndCream
├── CoffeeWithMilkAndSugar
├── CoffeeWithCreamAndSugar
├── ... (16 classes!)
Decorator Composition
Just 5 classes for infinite combinations!
Coffee (component)
├── MilkDecorator
├── CreamDecorator  
├── SugarDecorator
└── CaramelDecorator

// Mix and match!
Key Insight

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

Decorator Pattern - Coffee Shop
// ========== 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());
    }
}
Output:
Simple Coffee = $2.00
Simple Coffee, Milk = $2.50
Simple Coffee, Milk, Whipped Cream, Caramel = $3.80

4Real-World Applications

Java I/O Streams
new BufferedReader(new FileReader(file))
Classic decorator usage. BufferedReader decorates FileReader to add buffering.
Middleware
Express.js, Django middleware
Each middleware decorates the request/response with logging, auth, compression.
React HOCs
withRouter(withAuth(Component))
Higher-Order Components wrap components to add props and behavior.
Logging/Caching
CachingService(LoggingService(BaseService))
Add cross-cutting concerns without modifying core service.

5Interview Follow-up Questions

Interview Follow-up Questions

Common follow-up questions interviewers ask

6Key Takeaways

1Decorator adds behavior by wrapping objects.
2Uses composition over inheritance.
3Decorators implement same interface as component.
4Stackable at runtime - avoid class explosion.
5Used in: Java I/O, middleware, React HOCs.
6Watch for: ordering, many small classes, identity.