Component Interface
The Decorator pattern attaches additional responsibilities to an object dynamically.
Core Structure
┌──────────────────────────┐
│ Component (interface) │
├──────────────────────────┤
│ + operation(): Result │
└──────────┬───────────────┘
│
┌─────┴─────────────┐
│ │
┌────┴──────┐ ┌───────┴───────┐
│ Concrete │ │ Decorator │
│ Component │ │ (abstract) │
└───────────┘ ├───────────────┤
│ - wrapped: │
│ Component │
├───────────────┤
│ + operation() │
└───────┬───────┘
│
┌─────┴──────┐
│ │
┌────┴────┐ ┌────┴────┐
│ConcreteA│ │ConcreteB│
│Decorator│ │Decorator│
└─────────┘ └─────────┘
Component Interface
public interface DataSource {
void writeData(String data);
String readData();
}
// Core implementation
public class FileDataSource implements DataSource {
private String filename;
public FileDataSource(String filename) {
this.filename = filename;
}
@Override
public void writeData(String data) {
// Write to file
}
@Override
public String readData() {
// Read from file
}
}
Base Decorator
public abstract class DataSourceDecorator implements DataSource {
protected DataSource wrapped;
public DataSourceDecorator(DataSource source) {
this.wrapped = source;
}
@Override
public void writeData(String data) {
wrapped.writeData(data);
}
@Override
public String readData() {
return wrapped.readData();
}
}
Why Decorator Over Inheritance
| Inheritance | Decorator |
|---|---|
| Static, compile-time | Dynamic, runtime |
| Combines all behavior at once | Add behavior incrementally |
| Class explosion | Composable |
| Fixed at design time | Flexible at runtime |
Concrete Decorators
Concrete decorators add specific behavior to the wrapped component.
Compression Decorator
public class CompressionDecorator extends DataSourceDecorator {
public CompressionDecorator(DataSource source) {
super(source);
}
@Override
public void writeData(String data) {
String compressed = compress(data);
wrapped.writeData(compressed);
}
@Override
public String readData() {
String data = wrapped.readData();
return decompress(data);
}
private String compress(String data) {
// Compression logic
return Base64.getEncoder().encodeToString(data.getBytes());
}
private String decompress(String data) {
// Decompression logic
return new String(Base64.getDecoder().decode(data));
}
}
Encryption Decorator
public class EncryptionDecorator extends DataSourceDecorator {
private final SecretKey key;
public EncryptionDecorator(DataSource source, SecretKey key) {
super(source);
this.key = key;
}
@Override
public void writeData(String data) {
String encrypted = encrypt(data);
wrapped.writeData(encrypted);
}
@Override
public String readData() {
String data = wrapped.readData();
return decrypt(data);
}
private String encrypt(String data) {
// Encryption logic using key
return Base64.getEncoder().encodeToString(data.getBytes());
}
private String decrypt(String data) {
// Decryption logic using key
return new String(Base64.getDecoder().decode(data));
}
}
Buffering Decorator
public class BufferingDecorator extends DataSourceDecorator {
private final List<String> buffer = new ArrayList<>();
private final int bufferSize;
public BufferingDecorator(DataSource source, int bufferSize) {
super(source);
this.bufferSize = bufferSize;
}
@Override
public void writeData(String data) {
buffer.add(data);
if (buffer.size() >= bufferSize) {
flush();
}
}
@Override
public String readData() {
flush(); // Ensure buffer is written
return wrapped.readData();
}
private void flush() {
String combined = String.join("\n", buffer);
wrapped.writeData(combined);
buffer.clear();
}
}
Logging Decorator
public class LoggingDecorator extends DataSourceDecorator {
private final Logger logger;
public LoggingDecorator(DataSource source, Logger logger) {
super(source);
this.logger = logger;
}
@Override
public void writeData(String data) {
logger.info("Writing data: {} bytes", data.length());
wrapped.writeData(data);
logger.info("Write complete");
}
@Override
public String readData() {
logger.info("Reading data");
String data = wrapped.readData();
logger.info("Read complete: {} bytes", data.length());
return data;
}
}
Stacking Decorators
Decorators can be stacked to combine multiple behaviors.
Stacking Example
// File + Compression + Encryption + Logging
DataSource source = new LoggingDecorator(
new EncryptionDecorator(
new CompressionDecorator(
new FileDataSource("data.txt")
),
secretKey
),
logger
);
// Read flow:
// LoggingDecorator.readData()
// → EncryptionDecorator.readData()
// → CompressionDecorator.readData()
// → FileDataSource.readData()
// ← decompress
// ← decrypt
// ← log
source.writeData("Hello, World!");
// 1. Log: "Writing data: 13 bytes"
// 2. Encrypt: "SGVsbG8sIFdvcmxkIQ=="
// 3. Compress: "U0dWc2JHOD0="
// 4. Write to file
Java I/O as Decorators
// Java streams use the Decorator pattern
InputStream is = new FileInputStream("data.txt"); // Base
BufferedInputStream bis = new BufferedInputStream(is); // Buffering
DataInputStream dis = new DataInputStream(bis); // Data types
GZIPInputStream gzis = new GZIPInputStream(dis); // Decompression
// Each wraps the previous, adding behavior
Dynamic Decorator Stacking
public class DataSourceBuilder {
private DataSource source;
public DataSourceBuilder(String filename) {
this.source = new FileDataSource(filename);
}
public DataSourceBuilder withCompression() {
source = new CompressionDecorator(source);
return this;
}
public DataSourceBuilder withEncryption(SecretKey key) {
source = new EncryptionDecorator(source, key);
return this;
}
public DataSourceBuilder withLogging(Logger logger) {
source = new LoggingDecorator(source, logger);
return this;
}
public DataSourceBuilder withBuffering(int size) {
source = new BufferingDecorator(source, size);
return this;
}
public DataSource build() {
return source;
}
}
// Usage
DataSource source = new DataSourceBuilder("data.txt")
.withCompression()
.withEncryption(key)
.withLogging(logger)
.build();
Order of Decorators
┌─────────────────────────────────────────────────────┐
│ Order Matters! │
├─────────────────────────────────────────────────────┤
│ │
│ Logging → Encryption → Compression → File │
│ (Outer) (Inner) │
│ │
│ vs │
│ │
│ Compression → Encryption → Logging → File │
│ (Outer) (Inner) │
│ │
│ Different order = different behavior! │
│ │
└─────────────────────────────────────────────────────┘
Common Stacking Patterns
| Pattern | Stack |
|---|---|
| Secure Storage | File → Compression → Encryption → Logging |
| HTTP Pipeline | Request → Auth → Validation → Rate Limiting |
| Stream Processing | Input → Parsing → Transform → Output |
Practice Problems
Design a scalable Decorator Pattern system. Cover high-level architecture, data model, and API design.
Solution
// Complete system design:
// - Functional + Non-functional requirements
// - Capacity estimation
// - Data model (SQL/NoSQL choice)
// - API endpoints
// - Component architecture
// - Scaling strategy
// - Monitoring & reliabilityHow would you scale Decorator Pattern to handle 10x the current load? Identify bottlenecks and solutions.
Solution
// Scaling approach:
// 1. Load balancing
// 2. Database sharding/replication
// 3. Cache layer (Redis)
// 4. CDN for static assets
// 5. Async processing (queues)
// 6. Microservices decompositionAnalyze potential failure modes for Decorator Pattern and design mitigation strategies.
Solution
// Failure mitigation:
// 1. Redundancy (multi-AZ)
// 2. Circuit breakers
// 3. Retry with backoff
// 4. Dead letter queues
// 5. Health checks
// 6. Graceful degradationQuiz
1. What does the Decorator pattern do?
2. Why is Decorator preferred over inheritance for adding behavior?
3. What is the base decorator?
4. What is an example of Decorator in Java I/O?
5. Does the order of stacking decorators matter?
Flashcards
Question
What is the Decorator pattern?
Click to reveal answer
Answer
Attaches additional responsibilities to objects dynamically. Wraps objects to extend behavior while maintaining the same interface.
Question
Why Decorator over inheritance?
Click to reveal answer
Answer
Decorator adds behavior dynamically at runtime. Inheritance is static at compile-time and leads to class explosion.
Question
What is the base decorator?
Click to reveal answer
Answer
Abstract class implementing the Component interface. Wraps a Component instance and delegates calls. Concrete decorators extend it.
Question
Real-world Decorator example?
Click to reveal answer
Answer
Java I/O streams: BufferedInputStream wraps FileInputStream. HTTP middleware: auth, rate limiting, logging decorators.
Question
Does decorator stack order matter?
Click to reveal answer
Answer
Yes! Different stacking order produces different behavior. Outer decorators process first. Design order carefully.
Revision Notes
Key Takeaways
- 1.Decorator adds behavior dynamically by wrapping objects with the same interface
- 2.Base decorator wraps component and delegates; concrete decorators add behavior
- 3.Decorators are composable — stack multiple for combined behavior
- 4.Order of stacking matters and affects the result
- 5.Java I/O streams are a classic example of the Decorator pattern
Interview Tips
- •Show how Decorator enables flexible behavior addition in your design
- •Explain how Java I/O uses Decorator pattern
- •Discuss the order of decorators when stacking multiple behaviors
- •Compare Decorator vs inheritance for extending behavior
Cheat Sheet
Decorator Pattern - Cheat Sheet
Purpose:
Add behavior dynamically by wrapping objects.
Structure:
Component (interface)
├── ConcreteComponent (base)
└── Decorator (abstract)
└── ConcreteDecorators
Key Points:
- Same interface as wrapped object
- Base decorator delegates to wrapped
- Concrete decorators add behavior
- Stack decorators for combined behavior
- Order matters!
Benefits:
- Dynamic behavior addition
- Composable
- No class explosion
- Single Responsibility
Java Examples:
- I/O streams (Buffered, GZIP)
- Collections (Unmodifiable)
- Servlets (Filter)
Use Cases:
- Compression + Encryption
- Logging + Auth + Rate Limiting
- Stream processing