Home/Learn/Low Level Design/Decorator Pattern

Decorator Pattern

Intermediate
Structural Patterns

Attaches additional responsibilities to an object dynamically at runtime by wrapping it, as a flexible alternative to subclassing.

Overview

The Decorator pattern adds behavior to individual objects without altering the class hierarchy. A Decorator implements the same interface as the object it wraps (the Component) and holds a reference to a Component. On each operation, it adds behavior before/after delegating to the wrapped component. Decorators can be stacked: a CachedDataSource wrapping a LoggedDataSource wrapping a RealDataSource. Java I/O streams are the canonical example — BufferedInputStream decorates FileInputStream. The key advantage over inheritance is runtime composition — you can combine decorators without a class explosion.

Decorator Implementation

Define a Component interface. ConcreteComponent is the base implementation. Abstract Decorator implements Component and holds a Component reference. ConcreteDecorators extend Abstract Decorator and add behavior.

Java — Stacked Decorators (TextProcessor)
// Component interface
public interface TextProcessor {
    String process(String text);
}

// Concrete Component — base implementation
public class PlainTextProcessor implements TextProcessor {
    @Override
    public String process(String text) {
        return text; // just return as-is
    }
}

// Abstract Decorator — holds a reference to another TextProcessor
public abstract class TextProcessorDecorator implements TextProcessor {
    protected final TextProcessor wrapped;

    public TextProcessorDecorator(TextProcessor wrapped) {
        this.wrapped = Objects.requireNonNull(wrapped);
    }
}

// Concrete Decorator 1 — HTML escape
public class HtmlEscapeDecorator extends TextProcessorDecorator {
    public HtmlEscapeDecorator(TextProcessor wrapped) { super(wrapped); }

    @Override
    public String process(String text) {
        String processed = wrapped.process(text); // delegate first
        return processed
            .replace("&", "&")
            .replace("<", "&lt;")
            .replace(">", "&gt;");
    }
}

// Concrete Decorator 2 — Trim whitespace
public class TrimDecorator extends TextProcessorDecorator {
    public TrimDecorator(TextProcessor wrapped) { super(wrapped); }

    @Override
    public String process(String text) {
        return wrapped.process(text.trim()); // trim before delegating
    }
}

// Concrete Decorator 3 — Uppercase
public class UpperCaseDecorator extends TextProcessorDecorator {
    public UpperCaseDecorator(TextProcessor wrapped) { super(wrapped); }

    @Override
    public String process(String text) {
        return wrapped.process(text).toUpperCase(); // uppercase after delegating
    }
}

// Stacking decorators at runtime
TextProcessor processor = new UpperCaseDecorator(
                            new HtmlEscapeDecorator(
                              new TrimDecorator(
                                new PlainTextProcessor())));

String result = processor.process("  <hello world>  ");
System.out.println(result); // &LT;HELLO WORLD&GT;

Java I/O Streams & Decorator vs Inheritance

Java's I/O library is entirely built on Decorator pattern. InputStream is the Component; FileInputStream is ConcreteComponent; FilterInputStream is Abstract Decorator; BufferedInputStream, DataInputStream, CipherInputStream are ConcreteDecorators. Inheritance would require a class per combination (BufferedFileInputStream, CipherFileInputStream, etc.) — exponential explosion.

Java — java.io streams as Decorator pattern
// Java I/O — classic Decorator pattern in action
InputStream raw     = new FileInputStream("data.bin");       // ConcreteComponent
InputStream buffered = new BufferedInputStream(raw);          // Decorator: buffering
InputStream decoded  = new GZIPInputStream(buffered);         // Decorator: decompression
DataInputStream data = new DataInputStream(decoded);          // Decorator: typed reads

int value = data.readInt();  // reads 4 bytes, decompresses, buffers, from file
data.close();

// Equivalent with try-with-resources
try (DataInputStream dis = new DataInputStream(
        new GZIPInputStream(
          new BufferedInputStream(
            new FileInputStream("data.bin"))))) {
    while (dis.available() > 0) {
        System.out.println(dis.readInt());
    }
}

// Inheritance explosion (why NOT to use it):
// class BufferedFileInputStream extends FileInputStream { ... }
// class GzipBufferedFileInputStream extends BufferedFileInputStream { ... }
// class CipherGzipBufferedFileInputStream extends GzipBufferedFileInputStream { ... }
// → O(2^n) classes for n features — unmanageable

Key Points to Remember

  • 1Decorator and Adapter both wrap objects — Decorator keeps the same interface; Adapter changes it.
  • 2Decorators are stacked at runtime — the order matters (TrimDecorator before HtmlEscapeDecorator avoids escaping spaces).
  • 3Java I/O streams (BufferedInputStream, DataInputStream) are the canonical Decorator example.
  • 4Prefer Decorator over inheritance when combining features leads to a class explosion.
  • 5A Decorator does not know which concrete class it wraps — it depends on the Component interface.

Interview Questions

Sign in to ask Aria
1

How does the Decorator pattern differ from inheritance?

EasyAmazon
2

How are Java I/O streams an example of the Decorator pattern?

MediumGoogle
3

What is the difference between Decorator and Proxy patterns?

MediumNetflix
4

Can you stack decorators? What are the implications of ordering?

HardUber
5

Implement a logging decorator for a generic Repository interface.

MediumAtlassian

Ask Aria about Decorator Pattern

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…