Back to Design Patterns
Structural Pattern

Decorator Pattern

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

Intermediate10 min read

What is the Decorator Pattern?

The Decorator pattern is a structural design pattern that lets you wrap an object inside another object to add new behaviour to it — without modifying the original object's class and without creating a subclass for every combination of features.

Each decorator wraps the component, adds its own behaviour before or after delegating work to the wrapped object. You can stack multiple decorators on top of each other, creating a pipeline of responsibilities that are applied in order.

Think of it like wearing clothes: you are the base object. You put on a jacket (one decorator), then a scarf (another decorator), then gloves (yet another). Each layer adds new capability, and you can mix and match freely without creating a new "JacketScarfGlovePerson" class.

Key Idea: Extend an object's behaviour at runtime by wrapping it, rather than using inheritance. Each wrapper adds one focused responsibility.

Core Participants

Component

Component Interface

The common interface for both the real object and all its decorators. This is what makes them interchangeable — decorators implement the same interface as the object they wrap, so the client can't tell the difference.

Concrete Component

Concrete Component

The real, base object that does the core work. Decorators wrap this. It has no knowledge of being decorated — it simply fulfils the component interface.

Base Decorator

Base Decorator

An abstract class that implements the component interface and holds a reference to a wrapped component object. It delegates all operations to the wrapped object — concrete decorators override only what they need to change.

Concrete Decorator

Concrete Decorator

Adds specific extra behaviour before or after calling the wrapped object's method. Multiple concrete decorators can be stacked on the same component in any order.

!Problem It Solves

The Subclass Explosion Problem

Suppose you have a Coffee class and you want to support add-ons: Milk, Sugar, and Whip. Using inheritance, you'd need a separate subclass for every combination:

  • → CoffeeWithMilk
  • → CoffeeWithSugar
  • → CoffeeWithWhip
  • → CoffeeWithMilkAndSugar
  • → CoffeeWithMilkAndWhip
  • → CoffeeWithSugarAndWhip
  • → CoffeeWithMilkSugarAndWhip

With just 3 add-ons you need 7 subclasses. Add a 4th add-on → 15 classes. This grows as 2ⁿ. Completely unmanageable.

Example Without Decorator

// Problem: Subclass explosion using inheritance
class Coffee { ... }
class CoffeeWithMilk extends Coffee { ... }
class CoffeeWithSugar extends Coffee { ... }
class CoffeeWithMilkAndSugar extends Coffee { ... }
class CoffeeWithMilkAndWhip extends Coffee { ... }
// ... 2^n subclasses for n add-ons — impossible to maintain!

Solution With Decorator

// Solution: Compose behaviours by wrapping at runtime
Coffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);   // add milk
coffee = new SugarDecorator(coffee);  // add sugar
coffee = new WhipDecorator(coffee);   // add whip
// Any combination, zero new classes needed!

How It Works — Step by Step

1

Define a Common Interface

Both the real object and all decorators implement the same interface, making them interchangeable to any client code.

2

Create a Base Decorator

This class holds a reference to a wrapped component and delegates all method calls to it — a forwarding proxy by default.

3

Concrete Decorators Override

Each concrete decorator overrides only the method it needs, adding behaviour before or after calling super.method().

4

Stack at Runtime

Wrap the base object with as many decorators as needed, in any order. Each call travels through the decorator chain automatically.

Decorator vs Inheritance

Both add behaviour to a class — but they do it in fundamentally different ways:

AspectDecoratorInheritance
When appliedRuntime — wrap any timeCompile time — fixed at build
CombinationsAny order, unlimited stackingRequires new subclass per combo
Class count1 class per behaviourGrows as 2ⁿ combinations
Original classUntouched, unmodifiedMay need modification
PrincipleComposition over inheritanceIS-A relationship

Implementation

// Decorator Pattern — Coffee Order System
// 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 50.0;
    }
}

// Base Decorator — wraps a Coffee object
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

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

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 15.0;
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Sugar";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 5.0;
    }
}

class WhipDecorator extends CoffeeDecorator {
    public WhipDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Whip Cream";
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost() + 20.0;
    }
}

// Usage — stack decorators at runtime
public class CoffeeShop {
    public static void main(String[] args) {
        Coffee coffee = new SimpleCoffee();
        System.out.println(coffee.getDescription() + " = ₹" + coffee.getCost());
        // Simple Coffee = ₹50.0

        coffee = new MilkDecorator(coffee);
        System.out.println(coffee.getDescription() + " = ₹" + coffee.getCost());
        // Simple Coffee, Milk = ₹65.0

        coffee = new SugarDecorator(coffee);
        coffee = new WhipDecorator(coffee);
        System.out.println(coffee.getDescription() + " = ₹" + coffee.getCost());
        // Simple Coffee, Milk, Sugar, Whip Cream = ₹90.0
    }
}

When to Use Decorator

Adding Optional Features

When objects need optional add-ons that should be combinable in any order — like toppings on a pizza.

PizzaTopping, ImageFilter

Cross-Cutting Concerns

When you want to add logging, caching, or auth around operations without cluttering business logic.

LoggingDecorator, AuthDecorator

I/O Stream Processing

When processing data through multiple transformation steps — compress, then encrypt.

BufferedReader, GZIPOutputStream

Middleware Pipelines

When building request pipelines where each middleware wraps the next.

Express middleware, Django middleware

UI Component Enhancement

When a UI widget needs optional visual additions like scroll bars or shadows.

ScrollableWidget, BorderedWidget

Pros & Cons

Advantages

  • Open/Closed Principle compliant.
  • Single Responsibility Principle compliant.

Disadvantages

  • Hard to remove a specific wrapper from the stack.
  • Initial configuration code can look complex.