Skip to content
intermediatePhase 49 · Low-Level Design

Decorator Pattern

Add behavior to objects dynamically without modifying their class.

30m
0 problems
Topic Progress0%

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

0/3solved
Design Decorator Pattern System

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 & reliability
Decorator Pattern Scaling

How 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 decomposition
Decorator Pattern Failure Modes

Analyze 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 degradation

Quiz

1. What does the Decorator pattern do?

Question 1 options

2. Why is Decorator preferred over inheritance for adding behavior?

Question 2 options

3. What is the base decorator?

Question 3 options

4. What is an example of Decorator in Java I/O?

Question 4 options

5. Does the order of stacking decorators matter?

Question 5 options

Flashcards

Question

What is the Decorator pattern?

Answer

Attaches additional responsibilities to objects dynamically. Wraps objects to extend behavior while maintaining the same interface.

Question

Why Decorator over inheritance?

Answer

Decorator adds behavior dynamically at runtime. Inheritance is static at compile-time and leads to class explosion.

Question

What is the base decorator?

Answer

Abstract class implementing the Component interface. Wraps a Component instance and delegates calls. Concrete decorators extend it.

Question

Real-world Decorator example?

Answer

Java I/O streams: BufferedInputStream wraps FileInputStream. HTTP middleware: auth, rate limiting, logging decorators.

Question

Does decorator stack order matter?

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