DEV Community

EZHILARASI M
EZHILARASI M

Posted on

The Decorator Pattern in Java: Adding Behavior Without Breaking Everything

Decorator Design Pattern in Java: Add Features Without Changing Existing Code

Introduction

When developing Java applications, you may need to add new features to an existing object without modifying its source code. One way to achieve this is by using the Decorator Design Pattern.

The Decorator Pattern is a structural design pattern that allows behavior to be added to an object dynamically by wrapping it inside another object. It follows the Open/Closed Principle, which states that software entities should be open for extension but closed for modification.


Why Use the Decorator Pattern?

Imagine you have a coffee ordering system.

A customer may order:

  • Simple Coffee
  • Coffee with Milk
  • Coffee with Sugar
  • Coffee with Milk and Sugar

Creating separate classes for every combination would lead to a large number of subclasses. Instead, the Decorator Pattern allows us to add these features dynamically without creating new subclasses.


Structure of the Decorator Pattern

The pattern consists of four main components:

1. Component

Defines the common interface for all objects.

2. Concrete Component

Provides the basic implementation.

3. Decorator

Implements the component interface and stores a reference to a component object.

4. Concrete Decorator

Adds new functionality before or after calling the original object's methods.


Class Diagram

             Component
                 ▲
                 │
        ConcreteComponent
                 ▲
                 │
            Decorator
                 ▲
                 │
        ConcreteDecorator
Enter fullscreen mode Exit fullscreen mode

Java Implementation

Step 1: Create the Component Interface

interface Coffee {
    String getDescription();
    double getCost();
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Create the Concrete Component

class SimpleCoffee implements Coffee {

    @Override
    public String getDescription() {
        return "Simple Coffee";
    }

    @Override
    public double getCost() {
        return 50.0;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the Decorator

abstract class CoffeeDecorator implements Coffee {

    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }

    @Override
    public String getDescription() {
        return coffee.getDescription();
    }

    @Override
    public double getCost() {
        return coffee.getCost();
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a Concrete Decorator

class MilkDecorator extends CoffeeDecorator {

    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    @Override
    public String getDescription() {
        return coffee.getDescription() + " + Milk";
    }

    @Override
    public double getCost() {
        return coffee.getCost() + 20;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Test the Pattern

public class DecoratorDemo {

    public static void main(String[] args) {

        Coffee coffee = new SimpleCoffee();

        System.out.println(coffee.getDescription());
        System.out.println("Cost: ₹" + coffee.getCost());

        coffee = new MilkDecorator(coffee);

        System.out.println(coffee.getDescription());
        System.out.println("Cost: ₹" + coffee.getCost());
    }
}
Enter fullscreen mode Exit fullscreen mode

Output

Simple Coffee
Cost: ₹50.0

Simple Coffee + Milk
Cost: ₹70.0
Enter fullscreen mode Exit fullscreen mode

How It Works

  1. A SimpleCoffee object is created.
  2. The object provides the basic description and cost.
  3. A MilkDecorator wraps the original coffee object.
  4. The decorator adds extra behavior by updating both the description and the cost.
  5. The original class remains unchanged while new functionality is added dynamically.

Advantages

  • Follows the Open/Closed Principle.
  • Adds functionality without modifying existing code.
  • Eliminates the need for many subclasses.
  • Promotes reusable and maintainable code.
  • Features can be combined dynamically.

Disadvantages

  • Introduces additional classes.
  • Multiple decorators may make debugging harder.
  • The object structure becomes more complex as decorators increase.

Real-World Applications

The Decorator Pattern is commonly used in:

  • Coffee ordering systems
  • Pizza customization applications
  • Java Input/Output Streams
  • GUI frameworks
  • Notification systems
  • Logging frameworks

A well-known Java example is the java.io package, where classes like BufferedInputStream and DataInputStream decorate an existing InputStream by adding extra functionality.


When Should You Use It?

Use the Decorator Pattern when:

  • You need to add features at runtime.
  • You want to avoid creating many subclasses.
  • You want to extend functionality without modifying existing classes.
  • Multiple combinations of features are possible.

Conclusion

The Decorator Design Pattern is one of the most useful structural design patterns in Java. It provides a clean and flexible way to extend the behavior of objects without changing their original implementation. By wrapping objects instead of modifying them, developers can build applications that are easier to maintain, extend, and reuse.

If your application requires adding optional features dynamically, the Decorator Pattern is an excellent choice.

Top comments (0)