The Decorator Pattern is a structural design pattern that allows new behaviour to be added to an object dynamically without modifying its existing class.

Instead of extending a class through inheritance, the Decorator Pattern wraps the original object inside another object that provides additional functionality before or after delegating the request.

This approach makes it possible to combine multiple behaviours at runtime by stacking decorators around an object, resulting in greater flexibility than traditional inheritance.

The Decorator Pattern is commonly used for logging, compression, encryption, caching, and input/output streams where additional responsibilities can be attached dynamically.

Structure

The Decorator Pattern consists of the following participants.

1. The Component defines the common interface used by both concrete components and decorators.
2. The Concrete Component provides the original implementation.
3. The abstract Decorator implements the same interface while maintaining a reference to another component.
4. Concrete Decorators add additional behaviour before or after delegating the request to the wrapped component.

Java Implementation

Suppose an application generates reports. Initially, reports are generated as plain text.

Depending on business requirements, the report may also need to be compressed and encrypted before it is stored or transmitted.

Create the common component interface.
public interface Report {
    String generate();
}
Create the concrete component.
public class PlainTextReport implements Report {
    @Override
    public String generate() {
        return "Monthly Sales Report";
    }
}
Create the base decorator.
public abstract class ReportDecorator implements Report {
    protected final Report report;

    protected ReportDecorator(Report report) {
        this.report = report;
    }
}
Create a compression decorator.
public class CompressionDecorator extends ReportDecorator {
    public CompressionDecorator(Report report) {
        super(report);
    }

    @Override
    public String generate() {
        return "Compressed(" + report.generate() + ")";
    }
}
Create an encryption decorator.
public class EncryptionDecorator extends ReportDecorator {
    public EncryptionDecorator(Report report) {
        super(report);
    }

    @Override
    public String generate() {
        return "Encrypted(" + report.generate() + ")";
    }
}
The client combines decorators as required.
public class Main {
    public static void main(String[] args) {
        Report report =
                new EncryptionDecorator(
                        new CompressionDecorator(
                                new PlainTextReport()));
        System.out.println(report.generate());
    }
}
Output:
Encrypted(Compressed(Monthly Sales Report))

How It Works?

The client first creates a PlainTextReport, which provides the original implementation of the Report interface.

The report is then wrapped inside a CompressionDecorator. Instead of replacing the original implementation, the decorator delegates the request to the wrapped object and enhances the returned result.

Next, the compressed report is wrapped inside an EncryptionDecorator, which again delegates the request before applying its own behaviour.

Because every decorator implements the same interface as the original component, multiple decorators can be combined in any order to create different combinations of functionality without modifying the existing classes.

Advantages

1. It adds new behaviour without modifying existing classes.
2. Multiple decorators can be combined dynamically at runtime.
3. It provides greater flexibility than inheritance for extending functionality.
4. It follows the Open/Closed Principle by allowing new decorators to be introduced without changing existing code.

Disadvantages

1. Multiple decorators can increase the number of classes in the application.
2. Debugging may become more difficult when many decorators are stacked together.
3. Understanding the execution flow can be harder because behaviour is distributed across several decorator classes.

JDK Examples

The Decorator Pattern is widely used throughout the Java platform.

1. Classes such as BufferedInputStream, DataInputStream, and CheckedInputStream decorate an existing InputStream by adding buffering, data reading, or checksum functionality.

2. Similarly, BufferedReader decorates a Reader by adding efficient buffering while preserving the same interface.

Summary

The Decorator Pattern dynamically adds new responsibilities to an object by wrapping it inside one or more decorator objects.

By favouring composition over inheritance, the pattern provides a flexible and extensible way to enhance object behaviour while keeping existing classes unchanged.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer • Java • Python • Distributed Systems • AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

💬 Comments

Join the Discussion