Chaturmind
LearnDSASystem DesignBlogPremium
Sign inGet started
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML

Company

  • Blog
  • Premium
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
HomeLearnSystem DesignLow-Level Design & Design PatternsStructural Design Patterns
✓ FreeIntermediate· 6 min read

Decorator Pattern

Adding behavior to an object at runtime without touching its class — the Coffee/Milk/Sugar example, and why it avoids a combinatorial explosion of subclasses.

Published September 22, 2026


Decorator Pattern

The problem: combinations, not just types

Say you're modeling a coffee shop's menu: a base coffee, optionally with milk, optionally with sugar, optionally with whip. If you model each combination as its own subclass, you get a combinatorial explosion — Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, CoffeeWithMilkAndSugarAndWhip... every new optional add-on doubles the number of subclasses needed to cover every combination.

The Decorator fix: wrap, don't subclass

interface Beverage {
    double cost();
    String description();
}

class Coffee implements Beverage {
    public double cost() { return 2.00; }
    public String description() { return "Coffee"; }
}

// Decorators wrap a Beverage and ADD behavior, implementing the same interface
abstract class BeverageDecorator implements Beverage {
    protected final Beverage wrapped;
    BeverageDecorator(Beverage wrapped) { this.wrapped = wrapped; }
}

class Milk extends BeverageDecorator {
    Milk(Beverage wrapped) { super(wrapped); }
    public double cost() { return wrapped.cost() + 0.50; }
    public String description() { return wrapped.description() + " + Milk"; }
}

class Sugar extends BeverageDecorator {
    Sugar(Beverage wrapped) { super(wrapped); }
    public double cost() { return wrapped.cost() + 0.25; }
    public String description() { return wrapped.description() + " + Sugar"; }
}

class Whip extends BeverageDecorator {
    Whip(Beverage wrapped) { super(wrapped); }
    public double cost() { return wrapped.cost() + 0.75; }
    public String description() { return wrapped.description() + " + Whip"; }
}

Stacking decorators at runtime

Beverage order = new Whip(new Sugar(new Milk(new Coffee())));
System.out.println(order.description()); // "Coffee + Milk + Sugar + Whip"
System.out.println(order.cost());        // 2.00 + 0.50 + 0.25 + 0.75 = 3.50

Each decorator calls wrapped.cost()/wrapped.description() first, then adds its own contribution — a chain of delegating calls, each adding one increment. Any combination of add-ons is just a different order of wrapping, chosen at runtime, instead of a separate class that had to be written in advance for that exact combination.

Why this avoids the subclass explosion

With N optional add-ons, subclassing needs up to 2^N classes to cover every combination. Decorator needs exactly N decorator classes (one per add-on) — any combination is composed at runtime by nesting, not pre-declared at compile time. Adding a new add-on (say, Caramel) means writing exactly one new decorator class; it doesn't require touching or regenerating any existing combination classes.

Decorator vs Inheritance — the structural difference

Inheritance fixes behavior additions at compile time — a CoffeeWithMilk class is permanently "coffee with milk," full stop. Decorator adds behavior at runtime, through composition — the exact same Coffee instance can be wrapped differently in different code paths, or have its wrapping decided by user input ("add milk? add sugar?") rather than baked into a class hierarchy chosen ahead of time. This is the same underlying principle as "favor composition over inheritance" from the OOP refresher — Decorator is one of the concrete patterns that principle produces when applied to "add behavior dynamically."

Follow-up questions this topic invites — and their answers

Q: Is Java's BufferedReader(new FileReader(...)) an example of Decorator? A: Yes — this is the textbook real-world instance in the JDK itself: Reader/InputStream/OutputStream/Writer are all decorator-friendly interfaces, and wrapping a FileReader in a BufferedReader adds buffering behavior without FileReader itself needing to know or change.

Q: How is Decorator different from Proxy, since both wrap an object behind the same interface? A: Decorator's purpose is adding behavior (the wrapped object's own behavior still runs, plus more); Proxy's purpose is controlling access to the wrapped object (the access might be restricted, deferred/lazy, or redirected — the wrapped object's behavior might not run at all, e.g. an access-denied proxy). Structurally similar, semantically different intents (see Composite & Proxy).

Q: What's a real downside of Decorator? A: Deeply nested decorator chains can be hard to debug — a stack trace through five layers of wrapping, each delegating to the next, is harder to read than a single class, and the order of wrapping can matter in non-obvious ways (e.g. a logging decorator wrapped inside vs outside a caching decorator behaves very differently).

Q: Could you implement the Coffee example with a List<Addon> field instead of nested wrapper objects? A: You could, and for simple additive cost/description logic it's arguably simpler — but you'd lose Decorator's core property of each add-on implementing the full Beverage interface itself (so a decorated beverage can be passed anywhere a plain Beverage is expected, transparently) and its ability to override/intercept behavior more elaborately than a flat list of additive modifiers could.

Previous

Adapter & Facade

Next

Composite & Proxy

AI Tutor

Lesson: Decorator Pattern

Quick actions

AI responses can be inaccurate. Verify critical information.