Implementing the Decorator Design Pattern for Dynamic Behavior Extension

The Decorator pattern enables the dynamic addition of behaviors to an individual object without altering its underlying structure or inheritance hierarchy. It provides a flexible alternative to subclassing by wrapping objects with decorator classes that perform additional tasks before or after delegating to the wrapped component.

Architectural Components

  • Component: An interface or abstract class defining the contract for objects that can receive dynamic additions.
  • ConcreteComponent: The base implementation of the component that can be decorated.
  • Decorator: An abstract class that implements the Component interface while holding a reference to a Component object.
  • ConcreteDecorator: Classes that extend the Decorator to add specific responsibilities or modifications to the wrapped component.

Implementation Example: Customizing Food Orders

Consider a restaurant system where customers can order base items like noodles or rice and add extra toppings, each with its own cost. Using the Decorator pattern avoids creating a complex subclass hierarchy for every possible combination.

public abstract class Menu { 
    public abstract String getLabel();
    public abstract double calculatePrice();
}

public class Rice extends Menu {
    public String getLabel() { return "Rice"; }
    public double calculatePrice() { return 5.0; }
}

public abstract class ToppingDecorator extends Menu {
    protected Menu baseOrder;
    public ToppingDecorator(Menu base) { this.baseOrder = base; }
}

public class EggTopping extends ToppingDecorator {
    public EggTopping(Menu base) { super(base); }
    public String getLabel() { return baseOrder.getLabel() + " + Egg"; }
    public double calculatePrice() { return baseOrder.calculatePrice() + 1.5; }
}

public class Main {
    public static void main(String[] args) {
        Menu myOrder = new Rice();
        myOrder = new EggTopping(myOrder);
        System.out.println(myOrder.getLabel() + ": $" + myOrder.calculatePrice());
    }
}

Advantages

  • Scalability: It avoids "subclass explosion" where unique combinations of features lead to an unmanageable number of subclasses.
  • Adherence to Open/Closed Principle: New functionality can be introduced without modifying existing code.
  • Dynamic Composition: Behaviors can be added or removed at runtime, unlike inheritance which is fixed at compile-time.

Typical Use Cases

  • Extending objects when inheritance is prohibited (e.g., final classes) or impractical.
  • Adding responsibilities to objects that need to be transparent to the rest of the system.
  • Scenarios where features need to be toggled on or off dynamically during runtime execution.

Decorator Pattern in Java I/O

Standard Java library classes, specifically the I/O stream hierarchy, utilize this pattern. For instance, BufferedWriter wraps a Writer instance to add buffering capabilities, thereby improving performance without altering the core writing logic of the underlying stream.

Writer fileWriter = new FileWriter("data.txt");
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("Enhanced write performance");
bufferedWriter.close();

Decorator vs. Static Proxy

While both patterns implement a common interface and hold a reference to a target object, they serve different design goals:

  • Purpose: The Decorator is primarily concerned with enhancing the functionality of the target. A Static Proxy is typically used to control access, manage lifecycle, or hide the original object.
  • Instantiation: In a Decorator, the component is past to the constructor from an external source. In a Static Proxy, the proxy often manages the instantiation of the target internally, effectively shielding the client from the implementation details.

Tags: Design Patterns java Software Architecture Object Oriented Design

Posted on Tue, 04 Aug 2026 16:19:57 +0000 by MLJJ