Decorator Design Pattern

In this article, we’ll continue our java design pattern study by looking at the Decorator Design Pattern. It is one of the Structural Design Patterns.We’ll learn about this pattern is all about. After that, we’ll look at the pattern’s design advantages, usage, and disadvantages.

Decorator Design Pattern

The decorator design pattern is used to change an object’s functionality during runtime. Other instances of the same class will be unaffected, therefore each object will have its behavior changed. The Decorator design pattern is a structural design pattern that uses abstract classes or interfaces with composition to implement (similar to the Adapter, Bridge, and Composite patterns).

To expand the behavior of an object, we use inheritance or composition, but we do this at compile-time and applies to all instances of the class. At runtime, we can’t add new functionality or delete existing behavior — this is where the Decorator pattern comes in. Because we divide the functionality into classes with distinct areas of concern, decorator design patterns are most commonly employed to apply the Single Responsibility Principle.

In terms of structure, the decorator design pattern is like the chain of responsibility pattern. We also known the Decorator Pattern as Wrapper.

This article is part of our Java Design Pattern Series. In case you are starting on the design patterns, I will highly recommend starting from the beginning of the series for better understanding and clarity.

1. How to Implement Decorator Pattern

  • Create an interface.
  • Create concrete classes that implement the same interface as the abstract classes.
  • Create an abstract decorator class that implements the same interface as the above.
  • Create a concrete decorator class that extends the abstract decorator class mentioned above.
  • Now you may decorate interface objects with the concrete decorator class you generated earlier.
  • Finally, double-check the output.

2. Implement Decorator Pattern in Java

A typical example of a decorator design pattern is ice cream. You make a basic ice cream and then decorate it with whatever you choose. The added toppings alter the flavor of the plain ice cream. You are free to use many toppings. Below is an example of this scenario in action.

2.1. Create Interface
public interface Icecream {
  public String makeIcecream();
}
2.2. Step 2

We see an interface showing an ice cream above. I’ve kept things as straightforward as possible. I provide an actual implementation of this interface in the following class. On top of this base class, decorators will be added.

public class SimpleIcecream implements Icecream {

    @Override
    public String makeIcecream() {
        return "Base Icecream";
    }
}
2.3 Step 3

Following class is the decorator class. It serves as the decorator’s design pattern’s foundation. It has a property for the interface type. When a decorator is created using its function constructor, an instance is dynamically assigned. Once assigned, that instance method will be invoked.

abstract class IcecreamDecorator implements Icecream {

    protected Icecream specialIcecream;

    public IcecreamDecorator(Icecream specialIcecream) {
        this.specialIcecream = specialIcecream;
    }

    public String makeIcecream() {
        return specialIcecream.makeIcecream();
    }
}
2.4. Step 4

The next two classes are comparable. These are two concrete classes that implement abstract decorators. The base instance is given to the decorator during creation and assigned to the super class via the constructor. We invoke the base method in the makeIcecream method before its own addNuts function(). By including new stages, this addNuts() expand the behaviour.

public class NuttyDecorator extends IcecreamDecorator {

    public NuttyDecorator(Icecream specialIcecream) {
        super(specialIcecream);
    }

    public String makeIcecream() {
        return specialIcecream.makeIcecream() + addNuts();
    }

    private String addNuts() {
        return " + crunchy nuts";
    }
}
public class HoneyDecorator extends IcecreamDecorator {

    public HoneyDecorator(Icecream specialIcecream) {
        super(specialIcecream);
    }

    public String makeIcecream() {
        return specialIcecream.makeIcecream() + addHoney();
    }

    private String addHoney() {
        return " + sweet honey";
    }
}
2.5. Steps 5

I have created a simple ice cream and decorated that with nuts and on top of it with honey. Any number of decorators can be used in any arrangement. The key benefit of the decorator design pattern is its exceptional flexibility and ability to alter the behavior of a chosen instance at runtime.

public class TestDecorator {

    public static void main(String args[]) {
        Icecream icecream = new HoneyDecorator(new NuttyDecorator(new BasicIcecream()));
        System.out.println(icecream.makeIcecream());
    }
}
Output
Decorator Design Pattern

3. Class Diagram

For better understanding the decorator design pattern, here is the class diagram outlining the various components and their relations.

decorator design pattern
Decorator Design Pattern

3.1. Advantages

Let’s see some advantages of using this pattern:

  • It gives you more options than static inheritance.
  • Because changes are done by coding new classes, the object’s extensibility is increased.
  • It makes coding easier by allowing you to create a sequence of functionality from certain classes rather than having to code all the actions into the object.

3.2. Disadvantages

There are certain disadvantages of using this design pattern

  • It is difficult to remove a specific wrapper from the stack of wrappers.
  • It is difficult to create a decorator whose behavior is independent of the sequence in which the decorators are stacked.
  • The initial layer configuration code may appear to be quite nasty.

4. When to use Decorator Design Pattern

Here are some of the use cases when we can use the decorator pattern:

  • We want to assign responsibilities to objects transparently and dynamically without affecting the other objects.
  • We want to give an object duty that you might wish to change in the future.
  • Sub-classing is no longer a viable method of extending functionality.
  • We need to add extra behaviors to objects at runtime without disturbing the code that uses them and use the Decorator approach.
  • it’s inconvenient or impossible to extend an object’s behavior using inheritance. Use the pattern.

Summary

In this post, we talked about the Decorator Design Pattern. We saw some of the real-world examples along with what are some advantages of using this pattern. We also saw a Java implementation for this design pattern. You can always check our GitHub repository for the latest source code.

Here are some example from JDK using the same design pattern:

  • All java.io subclasses. The constructors for InputStream, OutputStream, Reader, and Writer all take an instance of the same type.
  • The checkedXXX(), synchronizedXXX(), and unmodifiableXXX() methods of the java.util.Collections package.
  • HttpServletRequestWrapper and HttpServletResponseWrapper are javax.servlet.http.HttpServletRequestWrapper and HttpServletResponseWrapper, respectively.
  • javax.swing.JScrollPane