How to Implement the Decorator Pattern for Dynamic Feature Addition in Java

The Decorator pattern lets you extend object behavior at runtime by wrapping components in decorator classes that implement the same interface, enabling dynamic feature addition without modifying original source code or creating subclass explosions.

The Decorator pattern is a structural design pattern that solves the problem of extending object functionality without inheritance. In the iluwatar/java-design-patterns repository, this pattern is demonstrated through a practical "troll" example that shows how to implement Decorator pattern for dynamic feature addition in Java by wrapping objects to augment their capabilities at runtime.

What Is the Decorator Pattern?

The Decorator pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality. Instead of creating multiple subclasses to combine various features, you create decorator classes that wrap the original component and add new behavior before or after delegating to the wrapped object.

This approach follows the Single Responsibility Principle by separating concerns into distinct classes and the Open/Closed Principle by allowing extension without modification.

Project Structure and Key Components

The implementation in iluwatar/java-design-patterns follows the classic Decorator structure with four main elements located in decorator/src/main/java/com/iluwatar/decorator/.

Component Interface

The Troll interface defines the contract that both concrete components and decorators must implement. Located in Troll.java, it declares three core operations:

public interface Troll {
    void attack();
    int getAttackPower();
    void fleeBattle();
}

Concrete Component

SimpleTroll in SimpleTroll.java provides the base implementation of the Troll interface. This represents the object to which additional features will be added dynamically:

@Slf4j
public class SimpleTroll implements Troll {
    @Override
    public void attack() {
        LOGGER.info("The troll tries to grab you!");
    }

    @Override
    public int getAttackPower() {
        return 10;
    }

    @Override
    public void fleeBattle() {
        LOGGER.info("The troll shrieks in horror and runs away!");
    }
}

Concrete Decorator

ClubbedTroll in ClubbedTroll.java demonstrates how to implement dynamic feature addition. It wraps a Troll instance and augments its behavior:

@Slf4j
@RequiredArgsConstructor
public class ClubbedTroll implements Troll {
    private final Troll decorated;

    @Override
    public void attack() {
        decorated.attack();
        LOGGER.info("The troll swings at you with a club!");
    }

    @Override
    public int getAttackPower() {
        return decorated.getAttackPower() + 10;
    }

    @Override
    public void fleeBattle() {
        decorated.fleeBattle();
    }
}

Implementing the Decorator Pattern Step by Step

To implement the Decorator pattern for dynamic feature addition in your own Java projects, follow this systematic approach demonstrated in the iluwatar/java-design-patterns repository.

Step 1: Define the Component Interface

Create an interface that declares the operations shared by concrete components and decorators. This ensures type compatibility and allows transparent wrapping.

Step 2: Create the Concrete Component

Implement the interface with your base class. This object represents the starting point to which you will add features dynamically.

Step 3: Build the Concrete Decorator

Create a decorator class that:

  • Implements the same interface as the component
  • Stores a reference to the wrapped object (typically via constructor injection)
  • Delegates calls to the wrapped object before or after adding new behavior

Step 4: Compose Objects at Runtime

In your client code, instantiate the concrete component, then wrap it with decorators as needed. The App.java file in the repository demonstrates this composition:

public class App {
    public static void main(String[] args) {
        // Simple troll
        Troll simpleTroll = new SimpleTroll();
        simpleTroll.attack();
        System.out.println(simpleTroll.getAttackPower());
        
        // Decorated troll with club
        Troll clubbedTroll = new ClubbedTroll(simpleTroll);
        clubbedTroll.attack();
        System.out.println(clubbedTroll.getAttackPower());
    }
}

How Runtime Decoration Works

The magic of dynamic feature addition happens through composition and delegation. When ClubbedTroll.attack() executes, it first calls decorated.attack() to preserve the original behavior, then adds the club swing. This chaining allows you to stack multiple decorators:

Troll superTroll = new ClubbedTroll(new SimpleTroll());
// Could add: new ArmoredTroll(new ClubbedTroll(new SimpleTroll()));

Because both decorators and components implement Troll, the client code in App.java treats decorated objects identically to simple ones. This transparent wrapping enables you to add responsibilities dynamically without affecting existing code or creating complex inheritance trees.

Summary

  • The Decorator pattern enables runtime behavior extension by wrapping objects in decorator classes that share the same interface as the components they decorate.
  • In iluwatar/java-design-patterns, the implementation uses Troll as the component interface, SimpleTroll as the concrete component, and ClubbedTroll as the concrete decorator located in decorator/src/main/java/com/iluwatar/decorator/.
  • Decorators maintain a reference to the wrapped object and use delegation to preserve original behavior while adding new features.
  • This approach follows the Open/Closed Principle, allowing you to add functionality without modifying existing source code or creating subclass explosions.

Frequently Asked Questions

What is the difference between the Decorator pattern and inheritance?

Inheritance extends behavior statically at compile time by creating new subclasses, which leads to rigid class hierarchies and the "class explosion" problem when combining multiple features. The Decorator pattern extends behavior dynamically at runtime through composition, allowing you to add and combine responsibilities by wrapping objects without creating new subclasses.

Can you stack multiple decorators on the same object?

Yes, you can stack decorators because both decorators and concrete components implement the same interface. You can wrap a SimpleTroll with a ClubbedTroll, then wrap that result with another decorator like ArmoredTroll, creating a chain of added behaviors while maintaining type compatibility with the original Troll interface.

When should you use the Decorator pattern versus the Strategy pattern?

Use the Decorator pattern when you need to add responsibilities to objects dynamically and transparently, and when extension by subclassing would produce too many classes to maintain. Use the Strategy pattern when you need to vary the entire algorithm or behavior of a class, rather than layering additional responsibilities around an existing object.

Does the Decorator pattern modify the original object?

No, the Decorator pattern does not modify the original object's code or internal state. It creates a new wrapper object that holds a reference to the original component and delegates calls to it. The original SimpleTroll object remains unchanged and unaware that it is being decorated, preserving the integrity of existing code while enabling new functionality.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →