Decorator Pattern
IntermediateAttaches 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.
// 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("<", "<")
.replace(">", ">");
}
}
// 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); // <HELLO WORLD>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 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 — unmanageableKey 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 AriaHow does the Decorator pattern differ from inheritance?
How are Java I/O streams an example of the Decorator pattern?
What is the difference between Decorator and Proxy patterns?
Can you stack decorators? What are the implications of ordering?
Implement a logging decorator for a generic Repository interface.
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.