STRUCTURAL PATTERNS / OBJECT DESIGN BRIEF

Decorator pattern

The Decorator pattern adds responsibilities to an object dynamically by wrapping it in another object with the same interface.

IntermediatePhase 05 / Topic 4 of 7ResponsibilitiesCollaborationsExtensibility
01

Overview

The Decorator pattern adds responsibilities to an object dynamically by wrapping it in another object with the same interface. Each decorator does its extra work and delegates to the wrapped object. Decorators stack: new Logging(new Retry(new Caching(httpClient))).

Decorators provide a flexible alternative to subclassing for extending behavior, avoiding class explosion for combinations of features. They are the object-oriented form of middleware and are perfect for cross-cutting concerns such as logging, caching, metrics, retries, authorization, and compression.

Wearing layers

You put on a shirt, then a sweater, then a raincoat. Each layer adds something (warmth, waterproofing) while you are still the same person, and layers can be combined in any order.

02

When to use it

  • Add features in combinations (milk + sugar + caramel).
  • Cross-cutting concerns around an existing interface.
  • You cannot or should not modify the original class.
03

Where it shows up in interviews

Add-ons pricing

Recognize it when: base item plus optional extras.

  • Design a coffee machine
  • Design a pizza ordering system
Cross-cutting concerns

Recognize it when: logging, caching, retry around a service.

  • Design an HTTP client library
  • Design a caching layer for a repository
04

Where it is used in real software

Java I/O streams

new BufferedInputStream(new GZIPInputStream(new FileInputStream(f))) stacks decorators.

Express/Koa middleware

Each middleware wraps the next handler with extra behavior.

Python decorators

@lru_cache and @retry wrap functions with caching and retry behavior.

05

Key terms

Component
Common interface (Beverage, HttpClient).
Concrete component
The base object being decorated.
Decorator
Implements the interface and wraps a component.
Stacking
Multiple decorators wrapping each other.
06

How it works, step by step

  1. 1
    Identify the component interface

    cost(), description().

  2. 2
    Implement the base component

    Espresso.

  3. 3
    Create a base decorator

    Holds a component and delegates.

  4. 4
    Add concrete decorators

    Milk, Caramel add behavior around delegation.

  5. 5
    Compose at runtime

    new Caramel(new Milk(new Espresso())).

07

Coffee with add-ons

new Caramel(new Milk(new Espresso()))

Step 1 / 3
Layercost()description()
Espresso2.00Espresso
Milk(Espresso)2.00 + 0.50 = 2.50Espresso, milk
Caramel(Milk(...))2.50 + 0.70 = 3.20Espresso, milk, caramel

NOWLayer: Espresso | cost(): 2.00 | description(): Espresso

Each decorator adds its part and delegates the rest; any combination works without new subclasses.

08

Implementation

public interface Beverage {    long costCents();    String description();} public final class Espresso implements Beverage {    public long costCents() { return 200; }    public String description() { return "Espresso"; }} public abstract class AddOn implements Beverage {    protected final Beverage inner;    protected AddOn(Beverage inner) { this.inner = inner; }} public final class Milk extends AddOn {    public Milk(Beverage inner) { super(inner); }    public long costCents() { return inner.costCents() + 50; }    public String description() { return inner.description() + ", milk"; }} public final class Caramel extends AddOn {    public Caramel(Beverage inner) { super(inner); }    public long costCents() { return inner.costCents() + 70; }    public String description() { return inner.description() + ", caramel"; }} Beverage order = new Caramel(new Milk(new Espresso())); // 320 cents
09

Complexity and performance

Call overheadO(decorators)

One delegation each.

Classes for n featuresn decorators

vs 2^n subclasses.

10

Trade-offs

Flexibility vs debuggability

Deep stacks of wrappers are harder to debug and stack traces get longer.

Order matters

Logging outside caching logs every call; inside it logs only misses.

11

Variants and related techniques

Function decorators

Higher-order functions that wrap functions.

Decorator vs Proxy

Same structure; Proxy controls access, Decorator adds behavior.

12

Common mistakes

  • Decorators that change the interface.

    Fix: That is an Adapter; decorators keep the same interface.

  • Relying on the concrete type after decorating.

    Fix: instanceof Espresso fails on a decorated object; use the interface.

13

Interview questions

Decorator vs inheritance for adding features?

Inheritance fixes features at compile time and needs a subclass per combination. Decorators add features at runtime by wrapping, and any combination is possible with one class per feature.

How would you add caching and retries to a repository without changing it?

Wrap it in decorators implementing the same interface: a CachingRepository that checks the cache before delegating and a RetryingRepository that retries transient failures.

14

Practice problems

ProblemDifficultyWhat it trains
Coffee machine with add-onsEasyStacking.
Decorators for logging, retry, and metrics on a clientMediumOrder of wrapping.