Decorator pattern — composition to extend behavior at runtime
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
Decorator adds behavior by wrapping an object instead of creating one subclass for each desired combination. The decorator implements or extends the same component abstraction, stores another component, delegates to it, and adds work around the call.
A familiar example is BufferedReader wrapping a FileReader. Both are usable as Reader; the outer object adds buffering while the inner object reads the file. More wrappers can be nested if their ordering and contracts are compatible. The pattern uses composition for the wrapped relationship, even though concrete decorators may share an abstract decorator superclass. I would describe Spring transaction proxies as related interception/proxy behavior rather than claim every proxy is exactly a GoF Decorator.
Decorator pattern — composition to extend behavior at runtime
Direct Answer
A Decorator holds a component that supports the same abstraction, delegates operations to it, and adds behavior before or after delegation. Wrappers can be nested to combine optional behavior without a subclass for each combination. The pattern uses composition internally, though a base decorator may also use inheritance, and the chain is often assembled at object creation rather than changed later.
Code Example
// Component interface
public interface DataSource {
String readData();
}
// Concrete component
public class FileDataSource implements DataSource {
private final String filename;
public FileDataSource(String filename) { this.filename = filename; }
@Override
public String readData() {
return "[file content from " + filename + "]";
}
}
// Base decorator holds wrapped component
public abstract class DataSourceDecorator implements DataSource {
protected final DataSource wrappee; // composition
protected DataSourceDecorator(DataSource source) {
this.wrappee = source;
}
@Override
public String readData() {
return wrappee.readData(); // delegates to wrapped
}
}
// Concrete decorator adds encryption
public class EncryptionDecorator extends DataSourceDecorator {
public EncryptionDecorator(DataSource source) { super(source); }
@Override
public String readData() {
var data = super.readData(); // delegate first
return "[encrypted: " + data + "]"; // add behavior after
}
}
// Concrete decorator adds compression
public class CompressionDecorator extends DataSourceDecorator {
public CompressionDecorator(DataSource source) { super(source); }
@Override
public String readData() {
var data = super.readData();
return "[compressed: " + data + "]"; // add behavior after
}
}
// Compose at runtime - no subclass explosion
DataSource source = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource("data.txt")));
String result = source.readData();
// result: "[compressed: [encrypted: [file content from data.txt]]]"
Common Mistakes & Pitfalls
- Saying Decorator avoids all inheritance: concrete decorators often share a base decorator.
- Promising exactly
2^Nsubclasses without decoration: that count assumes a specific naive inheritance design. - Calling every proxy a Decorator: proxy and interceptor designs can have different intent and lifecycle.
- Ignoring order: compression then encryption is not generally equivalent to encryption then compression.
- Calling runtime composition live replacement: many wrapper chains are built once and remain fixed.
Check your understanding
Answer these without looking back at the article.
- How does Java I/O use the Decorator pattern?
- What is the difference between Decorator and inheritance?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.