Decorator Design Pattern

Definition

The Decorator design pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Introduction

We will begin exploring the decorator design pattern with an example (as always :)). Let us say we have an object that represents a Computer. It has two methods – one to list the features of the computer and the second to return the price of it.

public abstract class Computer {
    public abstract void listFeatures();

    public abstract double getPrice();
}
public class BasicComputer extends Computer {
    @Override
    public void listFeatures() {
        System.out.println("Keyboard");
        System.out.println("Mouse");
        System.out.println("Monitor");
        //etc
    }

    @Override
    public double getPrice() {
        return 100.0;
    }
}

All good so far. Now, we want to offer the customers add ons or accessories on top of the basic computer when purchasing. For example: One may want to buy a speaker, an UPS or some adapters when buying the computer.
How can we represent or create classes for this?

Using Inheritance

We can inherit the BasicComputer to add these things. Say, we create a class that represents the basic computer along with the UPS as follows

public class BasicComputerWithUPS extends BasicComputer {
    @Override
    public void listFeatures() {
        super.listFeatures();
        System.out.println("Uninterrupted Power Supply");
    }

    @Override
    public double getPrice() {
        return super.getPrice() + + 40.0;
    }
}

For the listFeatures methods, we call the super method to print the basic features. Then, it prints the features related to the UPS. Similarly, it calls the super getPrice method to get the price of the basic computer, and then, adds on the price of the UPS.
For others, we would have to create such classes; like BasicComputerWithSpecialSpeakers, BasicComputerWithAdapters.

Problem with inheritance

This doesn’t sound as bad as it actually is. The real problem comes when we get into combinations (or more than one accessory) like BasicComputerWithSpecialSpeakersAndAdapters, BasicComputerWithSpecialSpeakersAndAdaptersAndUPS. This results in class explosion.

The Decorator

The problem with inheritance is that the extensions are defined at compile time itself. This is rigid. Decorator design pattern enables us to add new functionalities dynamically at runtime. We can easily mix and match new functionalities without impacting the existing classes. 

Decorator pattern – usage

To achieve this, we create decorator objects i.e., it decorates an object of the same type. First, we create a class called ComputerDecorator that extends Computer. It is important for the decorator class to have the same type as the object it decorates. Then we can wrap a 

  1. Basic computer in a FiveSetAdapters (giving us a computer with adapters)
  2. Wrap the above in a UPS (giving us a computer with adapters and a UPS)
  3. Wrap the above in a Special Speaker – (giving us a computer with adapters, UPS and speakers)
Since the decorated object has the same type as the original object, we can pass it around like the original object. A decorator adds new behaviour either before or after it invokes the underlying object’s behaviour (This will become clearer after seeing the example below) 
public abstract class ComputerDecorator extends Computer {
 // can add new methods if needed
}
public class UPS extends ComputerDecorator {
    private final Computer computer;

    public UPS(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void listFeatures() {
        computer.listFeatures();
        System.out.println("Uninterrupted Power Supply");
    }

    @Override
    public double getPrice() {
        return computer.getPrice() + 40.0;
    }
}

Let us look at the UPS class. It extends ComputerDecorator and has a Computer as the underlying (original) object. For the UPS decorator to add new functionality, it calls the underlying object’s listFeatures and then lists its own.Similarly, it calls the getPrice on the decorated or the wrapped object and then adds its cost returning the total cost.

This method of composing objects and calling methods on them in general is called delegationStrategy design pattern is another pattern that relies on composition over inheritance.

Let us complete the this by adding a couple of more decorators.

public class FiveSetAdapters extends ComputerDecorator {
    private final Computer computer;

    public FiveSetAdapters(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void listFeatures() {
        computer.listFeatures();
        System.out.println("Set of five adapters");
    }

    @Override
    public double getPrice() {
        return computer.getPrice() + 35.0;
    }
}
public class SpecialSpeaker extends ComputerDecorator {

    private final Computer computer;

    public SpecialSpeaker(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void listFeatures() {
        computer.listFeatures();
        System.out.println("20 KHz royal speaker");
    }

    @Override
    public double getPrice() {
        return computer.getPrice()
                + 30.5;
    }
}
public class Main {
    public static void main(String[] args) {
        Computer computer = new BasicComputer();
        computer.listFeatures();
        System.out.println("Total is " + computer.getPrice());

        System.out.println("-----");

        computer = new FiveSetAdapters(computer);
        computer = new UPS(computer);
        computer = new SpecialSpeaker(computer);

        computer.listFeatures();
        System.out.println("Total is " + computer.getPrice());

    }
}

Outputs:

Keyboard
Mouse
Monitor
Total is 100.0
-----
Keyboard
Mouse
Monitor
Set of five adapters
Uninterrupted Power Supply
20 KHz royal speaker
Total is 205.5

We have wrapped objects to three levels, getting a computer with UPS, adapters and speakers. This is the real power of composition.

Structure

Participants

Component (Computer)
  • Defines the interface for objects that can have responsibilities added to them dynamically.
ConcreteComponent (TextView)
  • It defines an object to which additional responsibilities are to be added.
Decorator
  • Maintains a reference to a Component object
  • Defines an interface that conforms to Component’s interface.
ConcreteDecorator (UPS, FiveSetAdapters)
  • Adds new responsibilities (at runtime) to the wrapped component.

Design principles

Open-Closed Principle: Classes must be open for extension and closed for modification. The decorator pattern enables us to do just that by adding new functionalities by wrapping objects rather than by changing existing code.

Conclusion

In this post we learnt what the decorator design pattern is and what it solves. We also looked at examples demonstrating the decorator pattern.

References

  1. Head First Design Patterns: A Brain-Friendly Guide by Eric Freeman and Elisabeth Robson.
  2. Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
  3. https://sourcemaking.com/design_patterns/decorator
  4. https://www.journaldev.com/1540/decorator-design-pattern-in-java-example

Leave a Reply