ReviseAlgo Logo

Structural Patterns

Decorator

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

Last Updated: June 26, 2026 25 min read

Decorator Pattern — Wrapping Layers

WhipCreamDecorator +$0.70
MilkDecorator +$0.50
SimpleCoffee $2.00
Result: "Simple Coffee, Milk, Whip Cream" = $3.20
Each decorator wraps the previous one, adding cost and description without modifying original class

The Decorator Pattern (also known as Wrapper) is a structural design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. By utilizing composition instead of inheritance, it bypasses the classic subclass explosion problem.

1. Learning Objectives

  • Identify the limitations of inheritance for compounding cross-cutting concerns.
  • Understand the structural layout of the Decorator pattern: Components, Concrete Components, Decorators, and Concrete Decorators.
  • Analyze JVM memory layouts, nested stack delegations, and heap references of layered wrappers.
  • Compare structural mechanics of Decorator with Proxy and Adapter patterns.
  • Implement thread-safe, typed decorators in Java, Python, and C++ (using smart pointer aggregation).

2. Problem & Naive Solution

Suppose you are building a POS system for a coffee shop. You start with a base Beverage class. Customers can customize beverages by adding toppings: Milk, Sugar, Whip Cream, Soy, or Caramel.

The Naive Solution (Subclass Explosion)

A naive implementation uses inheritance to model every permutation of beverage and toppings:

  • SimpleCoffeeWithMilk
  • SimpleCoffeeWithMilkAndSugar
  • SimpleCoffeeWithMilkSugarAndWhip
  • EspressoWithSoyAndCaramel

If you have 5 beverages and 5 toppings, you will end up with dozens of class files. This is known as Class Explosion.

Alternatively, you might add boolean flag fields to the base class:

This approach violates the Open/Closed Principle:

  • Adding a new ingredient (e.g. Soy) forces you to modify the base Beverage class.
  • Double servings of an ingredient (e.g. double sugar) cannot be easily tracked using simple booleans.
  • If the price of milk changes, you must rewrite the main cost calculation logic.

3. Issues

Relying on inheritance binds the subclass extension statically at compile-time. If a customer wants to dynamically add toppings at runtime or swap ingredients, inheritance fails completely because an object's class type cannot be changed after creation.

4. Pattern Introduction & UML

The Decorator Pattern shifts the architectural model from static subclassing to dynamic object aggregation. It defines a Wrapper class that implements the same interface as the wrapped object, delegating methods to the wrapped instance while adding custom behavior before or after execution.

UML: Decorator Structure

5. Participants

  • Component (Beverage): The common interface defining the operations that can be decorated dynamically.
  • Concrete Component (SimpleCoffee): The core object whose behavior will be extended.
  • Decorator (BeverageDecorator): An abstract class holding a reference to a Component and conforming to the Component's interface contract.
  • Concrete Decorator (MilkDecorator, SugarDecorator): Dynamic subclasses that add specific properties and forward calls to the parent delegate.

6. Theory (Dynamic Composition vs Inheritance)

Decorator leverages composition to attach behavior recursively:

  • Dynamic Behavior: Because a decorator implements the same interface as the wrapped object, a decorator can wrap another decorator. This allows you to construct deep nesting (e.g. Whip(Milk(Sugar(Coffee)))).
  • Comparison: - Decorator: Dynamically adds/enriches the interface's behavior at runtime. - Proxy: Manages lifecycle, security, or access to the underlying object. - Adapter: Modifies the interface format itself to bridge structural gaps.

7. Syntax Explanation

Key implementation constructs in different languages:

  • Java: Declares an abstract class extending the interface, keeping a reference to the delegate as a field (protected final Beverage decoratedBeverage;).
  • Python: Interfaces are implicit (Duck Typing). A decorator class intercepts methods via __getattr__ or overrides standard properties to invoke delegates.
  • C++: Uses std::unique_ptr composition to manage wrapping ownership, preventing resource leaks when nesting deep heap allocations.

8. Step-by-Step Implementation

  1. Step 1: Create the component interface outlining the shared contract methods (e.g. Beverage).
  2. Step 2: Implement concrete component classes representing base types (e.g. SimpleCoffee).
  3. Step 3: Create the abstract BeverageDecorator implementing Beverage, injecting a Beverage component reference via the constructor.
  4. Step 4: Build concrete decorators subclassing the abstract decorator, adding value to the methods by delegating, modifying arguments, or modifying return values.
  5. Step 5: Instantiate the components by nesting them dynamically (e.g. new MilkDecorator(new SugarDecorator(new SimpleCoffee()))).

9. Complete Code (Mini Project)

10. Code Walkthrough

Let's trace how the dynamic cost and description resolution works:

  • Recursive Call Execution: When the client calls getCost() on the outermost decorator (WhipCreamDecorator), it delegates execution by calling decoratedBeverage.getCost(). This chains down to MilkDecorator, then to SugarDecorator, and finally returns 2.0 from SimpleCoffee.
  • Bubbling Up: Each wrapping layer intercepts the return value of the delegate and appends its cost delta (e.g. adding +0.50 for milk, +0.20 for sugar) before passing it up the call stack.
  • Encapsulated Type Consistency: Because both decorators and components implement the common Beverage interface, they are polymorphic. The client is completely unaware that it is interacting with a wrapped chain rather than a simple class object.

11. Execution Flow

  1. Client Invocation: Client invokes getCost() on the wrapper instance.
  2. Downwards Delegation: Each decorator forwards the call to its internal Beverage reference.
  3. Core Execution: The concrete component (SimpleCoffee) calculates the base cost (2.00) and returns it.
  4. Upwards Accumulation: As the call stack unwinds, each layer adds its ingredient surcharge.
  5. Client Delivery: The aggregated total ($2.70) is returned to the client.

12. Internal Working (JVM & Heap Analysis)

Using deep decorator hierarchies affects runtime performance:

  • Recursive Stack Allocation: Nesting $N$ decorators creates $N$ nested method calls. Each call pushes a new stack frame onto the thread's execution stack, introducing stack frame lookup overhead.
  • Heap Reference Graphs: The decorators form a linked-list hierarchy on the heap. Instead of a single flat object allocation, memory is occupied by multiple small wrapper objects pointing to each other. This layout increases pointer-chasing cost during execution and creates GC work when dereferencing the wrapper chain.

13. Complexity Analysis

  • Time Complexity: $O(N)$ where $N$ is the number of decorators in the wrapper chain, due to recursive delegation.
  • Space Complexity: $O(N)$ runtime stack space and heap allocation space to manage the linked components.

14. Best Practices

  • Keep Interfaces Small: The component interface should focus strictly on fundamental behaviors. Decorating a large interface with 50 methods is difficult because every decorator must implement all 50 delegate methods.
  • Ensure Ordering Independence: Avoid building decorators that must run in a specific order (e.g. TaxDecorator must run *after* DiscountDecorator). Decorators should ideally be composable in any sequence.

15. Common Mistakes

  • Type Casting inside Clients: Checking if a component is an instance of a concrete decorator (e.g., if (beverage instanceof MilkDecorator)). This breaks runtime polymorphism and client-side decoupling.
  • Too Many Small Classes: Generating decorator classes for micro-behaviors that could easily be parameter fields in existing wrappers.

16. Framework Usage

  • Java I/O Streams: Standard libraries use decorator heavily:
InputStreamReader adapts the raw InputStream socket to a character stream; BufferedReader decorates it with buffer caching.
  • Spring Security: Wraps servlet requests to inject session authentication, role checks, and filter validations dynamically before invoking business controller targets.
  • 17. Interview Discussion

    Q: What is the main tradeoff between using a Decorator pattern vs Subclassing?
    Answer: Subclassing extends behavior statically at compile time, leading to class explosion for multi-axis configurations. Decorator extends behavior dynamically at runtime through composition, keeping the class count minimal at the expense of creating many small heap-allocated wrapper objects and deep recursive call stacks.
    Q: How do you handle cases where a decorator needs to modify or access a field not declared in the Component interface?
    Answer: If decorators require access to private state details, the pattern's clean encapsulation is compromised. This is a design smell indicating the Component interface is either too narrow, or that a Mediator/Strategy pattern would be a better fit.
    Q: Is it possible to remove a decorator from a wrapper chain at runtime?
    Answer: It is not supported by standard interfaces because the wrapping is uni-directional. To remove a decorator, you must reconstruct the object chain from the bottom up, skipping the target wrapper, or maintain an array representation of the decorators.

    18. Practice Exercises

    • Easy: Implement a SugarDecorator and WhipDecorator in Python, verifying cost sums and description outputs.
    • Medium: Design a TextFormatter system with BoldDecorator, ItalicDecorator, and UnderlineDecorator wrapping base text strings to output formatted HTML fragments.
    • Hard: Build an encryption/compression data stream wrapper. The base system writes raw bytes to a file. Design decorators that compress bytes (using GZip) and encrypt bytes (using AES) on-the-fly during streams.

    19. Challenge Problem

    Design a Dynamic Pricing Engine for a ridesharing application. The base cost of a ride depends on distance and time. However, rides can accrue multiple dynamic surcharges: Surge Pricing (multiplier), Night Surcharge (flat fee), Toll Road Surcharges (flat fee), and Promotional Discounts (percentage reduction). Design a decorator-based calculator. Surcharges must be stacked dynamically in any configuration. Surcharges that multiply cost must be calculated correctly with respect to flat fees. Write the implementation in Java, Python, or C++ and test it with multiple combinations of surcharges.

    20. Summary & Cheat Sheet

    • Decorator delegates execution to a wrapped component, attaching behavior before or after execution.
    • Avoid inheritance explosion by stacking lightweight wrappers.
    • Keep component interfaces small to simplify writing delegates.
    • Do not perform type casting (instanceof) on decorated instances.

    21. Quiz

    1. Which structural design pattern attaches responsibilities to objects dynamically at runtime?

    A) Adapter
    B) Proxy
    C) Decorator (Correct)

    2. What architectural problem does the Decorator pattern primarily solve?

    A) Tight database query coupling
    B) Class/subclass explosion (Correct)
    C) Socket connection timeout management

    3. How does the Decorator link to the Component it wraps?

    A) Via global database tables
    B) Via object composition by referencing the Component interface (Correct)
    C) Via static factory configuration maps

    4. Why is a Decorator class required to implement the same interface as the wrapped Component?

    A) To support polymorphic substitution and wrapper nesting (Correct)
    B) To access private class variables of the Component
    C) To enable garbage collection of the core object

    5. Which of the following is a classic example of the Decorator pattern in the Java Standard Library?

    A) BufferedReader wrapping InputStreamReader (Correct)
    B) ArrayList wrapping Arrays
    C) Math.max()

    6. What is the time complexity of resolving a call in a chain of $N$ decorators?

    A) $O(1)$
    B) $O(N)$ (Correct)
    C) $O(\log N)$

    7. What is a key performance drawback of using deeply nested decorators?

    A) Disables multithreading execution safety
    B) Creates recursive stack frame allocation and heap pointer chasing overhead (Correct)
    C) Increases static class compilation size

    8. How does Decorator differ from Adapter?

    A) Decorator extends functionality under the same interface; Adapter matches incompatible interfaces (Correct)
    B) Decorator modifies compile-time bytecodes; Adapter does not
    C) Decorator requires multiple class inheritance; Adapter uses composition

    9. Why should decorator component interfaces be kept small?

    A) To prevent stack-overflow limits
    B) To simplify implementation, as decorators must override and delegate all interface methods (Correct)
    C) To avoid double-checked locking hazards

    10. Can a decorator chain be altered easily by removing a middle wrapper?

    A) Yes, because Java references are bi-directional
    B) No, since wrapper pointers are uni-directional, rebuilding the chain is usually necessary (Correct)
    C) Only when using abstract class decorators

    22. Next Lesson Preview

    In the next lesson, we will explore the Composite Pattern. We will learn how to treat individual objects and compositions of objects uniformly, enabling recursive tree structure hierarchies!