Decorate your objects!

Decorator Decorator) is one of the basic design patterns of a group of structural patterns. It allows you to assign new behaviors to an object in a dynamic way, and does not use inheritance, thus guaranteeing great flexibility and extended functionality. New features are given by “packaging” the base object (or decorative objects) that is the same type as the decorated object. The UML diagram of this pattern is as follows:

UML diagram of decorator pattern classes
Figure. 1. UML diagram of decorator pattern classes

The reader’s attention is most simply caught by writing about delicious food. Although it is not a culinary blog, let’s move for a moment to a new burger shop in the city, whose owners, in a fit of creative thought, decided to give the name “DecoBurger”. The initial diagram of the classes of the in-premises order system for 3 burgers was as follows:

Initial DecoBurger Class Diagram
Figure. 2. Initial UML diagram of DecoBurger classes

With the growing popularity of DecoBurger, more and more customers began to visit, among whom there were also more demanding ones. This forced the owners to decide to introduce the possibility of adding ingredients to individual burgers, such as extra bacon, cheese or onions. The consequence of this decision was the need to redesign the system. The first thought was to create new classes, where each would represent a burger with the right additions/additions. The UML diagram would be as follows:

UML diagram after introducing the ability to add ingredients to burgers
Figure. 3. UML diagram after introducing the ability to add ingredients to burgers

At first glance, it is clear that this would not be a good and transparent solution. It would lead to the creation of a huge number of new classes. In addition, such code would be problematic in expansion and not flexible. It is easy to imagine a situation in which the introduction by the owners of the premises of a new addition to the burger would make it necessary to create another few classes. Another example would be a change in the price of any add-on, which would require you to change the code in several classes. And then he enters… all white. We are talking, of course, about the decorator pattern, the use of which will result in a simplification of the code:

UML diagram after introducing the ability to add ingredients to burgers with applying the decorator pattern
Figure. 4. UML diagram after introducing the ability to add ingredients to burgers with applying the decorator pattern

Let’s start coding with the Burger class definition:

public abstract class Burger {
    String description = "Burger unknown";

    public String download Description() {
        return description;
    }

    public abstract double calculateCene();
}

Next in the next is the abstract class Component Decorator:

public abstract class IngredientDecorator extends Burger {
    public abstract String downloadSwrite();
}

Once you’ve created base classes, you can start implementing individual burgers:

public class Binaryburger extends Burger {

    public Binaryburger() {
        description = "Binaryburger";
    }
    
    @Override
    public double calculatedCene() {
        return 10.10;
    }
}

, as well as additives (decorators):

public class Bacon extends ComponentDecorator {

    Burger burger;

    public Bacon (Burger burger) {
        this.burger = burger;
    }

    @Override
    public String download Description() {
        return burger.pobierz Description() + ", Bacon";
    }

    @Override
    public double calculatedCene() {
        return burger.calculatedCene() + 1.20;
    }
}

The code of subsequent burgers and add-ons differs only in price and description, so I leave them to readers as part of a training 👨 💻.

There is nothing left but to test our solution. To do this, a test class called Decoburger will be created:

public class Decoburger {

    public static void main(String[] args) {
        Burger burger1 = new Binaryburger();
        write description (burger1);

        Burger burger2 = new Cheeseburger();
        burger2 = new Bacon(burger2);
        write description (burger2);

        Burger burger3 = new Royalburger();
        burger3 = new Cheese(burger3);
        burger3 = new Onion(burger3);
        burger3 = new Bacon(burger3);
        write description (burger3);
    }

    static void write Description (Burger burger) {
        System.out.println(burger.pobierzOpis() + ", cost: " + burger.calculatedCene() + " zł");
    }
}

The result of running the code is as follows:

Binaryburger, cost: £10.1
Cheeseburger, Bacon, cost: £10.6
Royalburger, Cheese, Onion, Bacon, cost: £13.1

In this example, using the decorator pattern has resulted in a significant increase in code flexibility and increased the ease of maintaining and expanding it. This was also done by using open/closed design rules. Moreover, this solution allows you to add new behaviors during the operation of the program, through the use of compositions and delegations. This pattern provides a strong alternative to inheritance in the context of functionality. However, it should be borne in mind that the abuse of decorators leads to an increase in the complexity of the code.

All the code from the entry is on my Githubie.

Sources: Eric Fre
[1]eman, Elisabeth Freeman, Kathy Sierra, Bert Bates: Design Patterns. Move your head!

Leave a Comment

Your email address will not be published. Required fields are marked *