Decorator Pattern in Java – Add Behavior Dynamically without Modifying Code

Illustration for Decorator Pattern in Java – Add Behavior Dynamically without Modifying Code
By Last updated:

Introduction

The Decorator Pattern is a structural design pattern that lets you attach new responsibilities to an object dynamically without altering its structure. It provides a flexible alternative to subclassing for extending functionality.

Why Decorator Pattern Matters

Imagine you have a TextView component and want to add scrolling, border, and highlighting. Instead of creating dozens of subclasses like ScrollTextViewWithBorder, you can wrap behaviors using decorators at runtime.

This makes the Decorator Pattern a great choice for flexible, extensible design.


Core Intent and Participants

  • Intent: Attach additional responsibilities to an object dynamically without modifying its code. Decorators provide a flexible alternative to subclassing.

Participants

  • Component: Defines the interface for objects that can have added behavior.
  • ConcreteComponent: Implements the base behavior.
  • Decorator: Implements the same interface and wraps a component.
  • ConcreteDecorator: Adds specific behavior.

UML Diagram (Text)

+------------------+
|   Component      |
+------------------+
| + operation()    |
+------------------+
        ^
        |
+------------------+       +----------------------+
| ConcreteComponent|       |     Decorator        |
+------------------+       +----------------------+
| + operation()    | ----> | - component:Component |
                         | + operation()         |
                         +----------------------+
                                   ^
                                   |
                      +---------------------------+
                      |   ConcreteDecoratorA      |
                      |   ConcreteDecoratorB      |
                      +---------------------------+

Real-World Use Cases

  • Java I/O Streams (BufferedReader, DataInputStream)
  • UI elements with dynamic styling
  • Logging enhancements (add timestamp, formatters)
  • Encryption/wrapping of data packets

Java Implementation Strategy

Example: Coffee Shop – Add Milk, Sugar, and Whipped Cream

Step 1: Component Interface

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

Step 2: Concrete Component

public class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }

    public double cost() {
        return 5.0;
    }
}

Step 3: Decorator

public abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

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

Step 4: Concrete Decorators

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

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

    public double cost() {
        return decoratedCoffee.cost() + 1.5;
    }
}

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

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

    public double cost() {
        return decoratedCoffee.cost() + 0.5;
    }
}

Step 5: Client Code

public class DecoratorDemo {
    public static void main(String[] args) {
        Coffee coffee = new SimpleCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SugarDecorator(coffee);

        System.out.println(coffee.getDescription()); // Simple Coffee, Milk, Sugar
        System.out.println("Cost: $" + coffee.cost()); // Cost: $7.0
    }
}

✅ You can keep stacking decorators without changing the base class.


Pros and Cons

✅ Pros

  • Adheres to Open/Closed Principle (open for extension, closed for modification)
  • Avoids class explosion caused by inheritance
  • Combines behaviors dynamically at runtime

❌ Cons

  • Many small classes to manage
  • Debugging can be harder due to multiple layers of wrapping

Anti-Patterns and Misuse

  • Using decorators when subclassing would be simpler
  • Over-decorating objects leading to performance issues
  • Misnaming decorators (hard to distinguish behavior chain)

Decorator vs Adapter vs Proxy

Pattern Goal Modifies Interface? Real-World Analogy
Decorator Add behavior dynamically ❌ No Toppings on a pizza
Adapter Convert one interface to another ✅ Yes Travel power plug adapter
Proxy Control access to an object ❌ No Bank ATM as proxy to bank

Refactoring Legacy Code

Before

public class LatteWithSugarAndCream extends Latte {
    public String getDescription() { return "Latte, Sugar, Cream"; }
    public double cost() { return 8.0; }
}

After (Using Decorator)

Coffee coffee = new Latte();
coffee = new SugarDecorator(new CreamDecorator(coffee));

✅ Eliminates the need for subclass explosion.


Best Practices

  • Keep decorators interchangeable by maintaining consistent interfaces
  • Compose instead of inherit for dynamic behavior
  • Name decorators clearly to reflect added behavior
  • Avoid nesting too deep—keep it readable

Real-World Analogy

Think of ordering a burger. You start with a base patty and add toppings: cheese, lettuce, tomato, sauces. You don’t need a separate class for every combination; toppings (decorators) add behavior on top of the base.


Java Version Relevance

  • Java 8+: Use lambdas for functional-style decorators
  • Java I/O Streams: Classic use-case of decorators (InputStream, BufferedInputStream)

Conclusion & Key Takeaways

  • Decorator Pattern adds functionality at runtime without modifying existing code.
  • Ideal for building flexible and extensible object behaviors.
  • Prefer composition over inheritance where behavior varies.
  • Use it in UI, logging, I/O streams, and formatting pipelines.

FAQ – Decorator Pattern in Java

1. What is the Decorator Pattern?

A structural design pattern to add behavior to objects dynamically.

2. When should I use it?

When you need to enhance object behavior without changing their class.

3. What’s the real Java example of this pattern?

Java I/O (BufferedReader, DataOutputStream)

4. Does it replace inheritance?

Yes, it avoids deep inheritance trees.

5. Can decorators be stacked?

Yes, decorators can be nested as much as needed.

6. Is it test-friendly?

Yes. You can test each decorator independently.

7. What if I need to remove a decorator?

You’ll need to manage composition manually—there’s no automatic removal.

8. Can I use it with Spring Beans?

Yes, by wrapping beans at runtime or using @Primary and profiles.

9. Does this pattern impact performance?

Slightly. Too many wrappers can add overhead.

10. Is this the same as middleware?

Middleware chains often use the same principles, so yes, conceptually similar.