Java 3 min read

The Decorator Design Pattern: Dynamic Superpowers

Master the Decorator Pattern in Java. Learn how to add features to objects dynamically without inheritance explosions. The ultimate 'Wrapper' pattern.

MR

Moshiour Rahman

Advertisement

The Problem: Class Explosion

Imagine you run a Coffee Shop. You have a class Beverage.

class Beverage { ... }
class DarkRoast extends Beverage { ... }
class Espresso extends Beverage { ... }

Now customers want Toppings: Milk, Mocha, Soy, Whip.

If you use inheritance, you end up with a nightmare:

  • DarkRoastWithMilk
  • DarkRoastWithMilkAndMocha
  • EspressoWithSoyAndWhip

This is the “Class Explosion” anti-pattern. You cannot create a class for every possible combination.

The Solution: The Decorator Pattern

The Decorator Pattern allows you to attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

Real-Life Analogy: Ordering Coffee ☕

You don’t buy a pre-manufactured “Soy Mocha Espresso”.

  1. You take a cup.
  2. You add Espresso (Base Component).
  3. You wrap it with Mocha (Decorator).
  4. You wrap it with Soy (Decorator).
  5. You wrap it with Whip (Decorator).

The final object is a big ball of wrappers, but it still looks like a Beverage.

Visualizing the Pattern

Decorator Pattern

Implementation

1. The Component Interface

public interface Beverage {
    String getDescription();
    double cost();
}

2. Concrete Component (The Base)

public class Espresso implements Beverage {
    @Override
    public String getDescription() {
        return "Espresso";
    }

    @Override
    public double cost() {
        return 1.99;
    }
}

3. The Decorator (The Wrapper)

This is the magic part. It is a Beverage, but it also has a Beverage.

public abstract class CondimentDecorator implements Beverage {
    protected Beverage beverage; // The object we are wrapping

    public CondimentDecorator(Beverage beverage) {
        this.beverage = beverage;
    }
}

4. Concrete Decorators

public class Milk extends CondimentDecorator {
    public Milk(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Milk";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.50; // Delegate + add own cost
    }
}

public class Mocha extends CondimentDecorator {
    public Mocha(Beverage beverage) {
        super(beverage);
    }

    @Override
    public String getDescription() {
        return beverage.getDescription() + ", Mocha";
    }

    @Override
    public double cost() {
        return beverage.cost() + 0.99;
    }
}

Usage

// 1. Order an Espresso
Beverage myDrink = new Espresso();

// 2. Add Milk
myDrink = new Milk(myDrink);

// 3. Add Mocha
myDrink = new Mocha(myDrink);

// 4. Calculate final cost
System.out.println(myDrink.getDescription()); // "Espresso, Milk, Mocha"
System.out.println(myDrink.cost());           // 1.99 + 0.50 + 0.99

In The Wild (Real World Examples)

1. java.io Package

The most famous example. The I/O library is purely built on decorators.

// FileInputStream is the base Component
// BufferedInputStream is a Decorator (adds buffering)
// GZIPInputStream is a Decorator (adds compression)
InputStream in = new GZIPInputStream(
    new BufferedInputStream(
        new FileInputStream("test.txt")
    )
);

2. UI Frameworks (Swing/JavaFX)

Adding borders or scrollbars to a window often involves wrapping the Window object in a BorderDecorator or ScrollDecorator.

Cheat Sheet

FeatureDetails
CategoryStructural
Problem SolvedAdding responsibilities to objects dynamically without inheritance
Key implementationclass Decorator implements Component { Component wrapped; }
ProsFlexible (add remove features at runtime), Composability
ConsCan result in many small objects and “wrapper hell” (hard to debug)

Tips to Remember 🧠

  • “Matryoshka Doll”: Decorator is like Russian nesting dolls. Each doll fits inside another.
  • “Wrapper”: Like Adapter, it’s a wrapper. But Adapter changes interface, Decorator adds behavior.
  • Recursive: Calls usually go this.wrapped.method() recursively until the base component is hit.

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.