Decorator Design Pattern

Design Pattern Tutorial Series

Decorator Design Pattern in Java - 5-Step Guide

The reasoning for using the Decorator Pattern is to obtain the advantages of class inheritance within a dynamic context. (aka. at runtime.) This allow objects to add and remove additional features while the program is running instead of specifically declaring it when the program compiles.

Similarly, to other design patterns that I have already investigated and explain, the decorator pattern exploits composition of classes instead of inheritance to gain flexibility at runtime and avoid class explosions.

Although the decorator pattern allows objects to add and remove functionalities, which could be indication of a behavioral design pattern, it is actually categorized as a structural design pattern. This is because it is not focused on the behavior itself, but the structure of the classes, which enables this design pattern.

Table of Contents

Decorator-Pattern-Whiteboard - INTEGU

Step 1 - The Objective

The objective of this example will be to make a boat producing factory. At this factory, the owner provides the customer with a plain boat as default and allows them to modify it by adding any type of equipment to it. Thereby the customer can make the plain boat into whatever boat they want. (speed boat, sailboat, yacht etc.)

The owner already has all the equipment, but dependent on the market and storage the prices might vary. It is therefore important to have an assembly system, which requires as little as possible modification if and when prices change.

Let’s start by seeing how we could solve the problem with the basic object-oriented approach of class inheritance. 

Decorator-Pattern-Objective - INTEGU

Step 2 - Changing Equipment with Inheritance

Interface

To solve the objective with class inheritance, we create a simple Boat interface, which sets the rules of all variations of boats. In this tutorial, we will simplify things and say that all boats only have two methods: getCost() and getDescription(). These two methods are therefore declared in the boat interface.

With the basic rules for all boat specified, we start making individual classes for each variation of boat and equipment. These classes are also boats and therefore implements the Boat interface.

Implementation 

When implementing, it is of cause required that we also provide the classes’ own implementation of the interface methods. In this implementation we therefore provide the specific cost and description of the boat and equipment.

We continue to make more classes like this until we have a class for each variation.

How to use it

Finally, when we want to create the different versions of boats, we initialize them within the main-method. As we can see, each boat version requires that their specific boat class is being initialized. 

public interface Boat {
    int getCost();
    String getDescription();
}
public class BoatWithEngineAndRoof implements Boat {
    @Override
    public int getCost() {
        return 22500;
    }
    @Override
    public String getDescription() {
        return "Plain Boat. Equipment: n Engine n Roof";
    }
}
public class Main {
    public static void main(String[] args) {
        Boat boat = new BoatWtihEngine();
        printBoatDescription();
        boat = new BoatWithEngineAndRoof();
        printBoatDescription();
    }
    private static void printBoatDescription(Boat boat) {
        System.out.println(boat.getCost());
        System.out.println(boat.getDescription());
    }
}

Although this example solves the objective, it also creates several problems. The usual problem which occurs when choosing inheritance over composition. Firstly, no flexibility to modify the object at runtime and secondly, a potential class explosion when we have to create a class for each variation of boats.

This is actually the same problems we experience in the strategy pattern tutorial. Both design patterns are based on composition of objects instead of inheritance. However, each pattern is designed to tackle a different code problem.

Additionally, we also experience a problem every time one of the equipment pieces change in price. In
that case, we would have to manually find all versions of the boat classes which use that piece of equipment and recalculate the price one by one. Combining this with a potential class explosion, and we got ourselves a big workload every time the price of just one piece of equipment change.

With the disadvantages of the classic object-oriented approach highlighted, we will now determine if the decorator pattern can help us.

 

Decorator-Pattern-Advantages-and-Disadvantages - INTEGU

Step 3 - Concept of The Decorator Pattern

The concept of the decorator pattern is to build objects around a common basic object or multiple objects, which can then add or remove different variations of additional functionalities. The adding and removing of functionalities is known as “decorating”.

By having the most basic version of the object (the component) set the foundation for all versions of the object, it is possible to build upon that with the decorator objects. This give the advantage that we can modify the price of the individual pieces of equipment from their respective classes. This means that we will only have to change the price variable one place. A lot less work for us on the long run.

Additionally, we do not get any class explosions, since we only need one class for each piece of equipment and not for each variation.

And finally, we will actually be able to modify the combination of equipment on the boat at runtime.

Thereby, by utilizing the decorator design pattern, we manage to fulfill all of the requested objectives of the example.  That all sounds great! So, let’s dive into the class diagram and code to see how we actually implement the decorator pattern.

Decorator-Pattern-Advantages - INTEGU

Step 4 - Class Diagram (UML)

As mentioned in the previous section, we have the most basic version of a boat available to build upon. However, before making the class representing this plain boat, we want to set some rules for all boats, so that we know what to expect when adding and removing equipment. We will come back to this point later.

Object Interface

To get a common understanding of what a boat should be able to do, we make the Boat-interface, which declares that any implementation of a boat should be able to provides its cost and a description of the boat.

Basic Object Implementation

With the interface established, we then create the PlainBoat-class which implements the Boat-interface and its methods. In the method, we only state the cost and description of the plain boat. That is all! The boat is done. Let’s now move onto the equipment aka. the decorations.

Abstract Decorator

In order to decorate our PlainBoat-class we need to make a decorator class, which allows us to use multiple decorators on the same PlainBoat. Since our decorations will be different types of equipment, we will name the decorator class EquipementDecorator.

Within the EquipementDecorator-class we want the constructor to get an instance of the Boat-interface so that we know which basic boat we are using. In this example we only have one basic boat, the PlainBoat-class, but in theory, the decorator pattern would enable us to have multiple versions of basic boats to decorate on.

We also want the EquipementDecorator-class to implement the Boat-interface, so that it can mirror the behavior of a boat. However, instead of writing its own implementations of the two methods (getCost() and getDescription()), the EquipementDecorator-class instead call the Boat-object’s versions of the same methods.

So, what does our EquipementDecorator-class actually do? Take in a Boat-object and call its method when the EquipementDecorator’s own method are call? Seems like a useless class, right? Well not actually, because we are going to use it as an extension for all the equipment classes.

Since the EquipementDecorator-class, is not actually going to be used as an independent equipment type, we do not want the user to initialize it. Therefore, we make the class into an abstract class, thereby only allowing it to be initialized through its extensions.

Implementation Of Decorator

We are going to use the engine as the example for how to create an equipment-decorator-class. The EngineEquipement-class is going to extend the abstract EquipementDecorator-class, which means that we need to implement the Boat-interface’s methods and a constructor, which takes in a Boat-object as the argument.

In the constructor we are just going to call the superclass’ constructor. Thereby allowing us to access the Boat-object through the use of the EquipementDecorator-class’ field.

In the implemented methods we also call the superclass’ version of the method, with the addition of adding the cost and description of the piece of equipment, in this case the engine. That’s it! We can now copy the pattern of the EngineEquipement-class and quickly add more types of equipment. 

With this in-depth explanation of the decorator patterns class diagram, time has come to jump into the code.

Decorator-Pattern-Class-Diagram-UML- INTEGU

Step 5 - Code (Java)

Given that code is sometimes difficult to learn through concepts and theory, I provide the code for the example.

Hopefully, this will be sufficient for you to learn to identify and implement the decorator pattern in your future endeavors as a software developer.

The Component (Boat)

public interface Boat {
    int getCost();
    String getDescription();
}
public class PlainBoat implements Boat {
    @Override
    public int getCost() {
        return 20000;
    }
    @Override
    public String getDescription() {
        return "Plain Boat. Equipment: ";
    }
}

The Decorator (Equipment)

public abstract class EquipmentDecorator implements Boat{
    private final Boat boat;
    public EquipmentDecorator(Boat boat) {
        this.boat = boat;
    }
    @Override
    public int getCost() {
        return boat.getCost();
    }
    @Override
    public String getDescription() {
        return boat.getDescription();
    }
}
public class EngineEquipment extends EquipmentDecorator {
    public EngineEquipment(Boat plainBoat) {
        super(plainBoat);
        System.out.println("Engine added to the boat");
    }
    @Override
    public int getCost() {
        return super.getCost() + 2000;
    }
    @Override
    public String getDescription() {
        return super.getDescription() + "n Engine";
    }
}

How To Use The Decorator Pattern

public class Main {
    public static void main(String[] args) {
        Boat boat = new EngineEquipment(new PlainBoat());
        printBoatDetail(boat);
        boat = new RoofEquipment(boat);
        printBoatDetail(boat);
    }
    private static void printBoatDetail(Boat boat) {
        System.out.println(boat.getCost());
        System.out.println(boat.getDescription());
    }
}

Recommended Reading

Article

  • Theoretical walkthrough – Source Making – Blog
  • Practical walkthrough – Tutorialspoint – Blog
  • Practical walkthrough – Springframework Guru – Blog

Tools

  • Camtasia – Used for illustrations – Camtasia

Books

Video Tutorial 

  • Theoretical  (based on “Head-First: Design Patterns”) – Christopher Okhravi – YouTube
  • Practical – Derek Banas – YouTube

About

Hi, I'm the Author

My name is Daniel H. Jacobsen and I’m a dedicated and highly motivated software developer with a masters engineering degree within the field of ICT. 

I have through many years of constantly learning and adapting to new challenges, gained a well-rounded understanding of what it takes to stay up to date with new technologies, tools and utilities. 

The purpose of this blog is to share both my learnings and knowledge with other likeminded developers as well as illustrating how these topics can be taught in a different and alternative manner.

If you like the idea of that, I would encourage you to sign up for the newsletter.

Cheers! 🍺

Didn't Find What You Were Looking For?

Generic selectors
Exact matches only
Search in title
Search in content
Post Type Selectors
Scroll to Top
INTEGU - Cookie-consent

INTEGU uses cookies to personalize your experience and provide traceability for affiliate links. By using the website, you agree to these terms and conditions. To learn more see the privacy policy page.