DEV Community

Iyanu Falaye
Iyanu Falaye

Posted on Edited on

Design Patterns: Decorator

Decorator pattern is a structural design pattern. It involves adding new functionality to an existing object without necessarily altering its structure. The Decorators also known as wrappers wrap the existing object and then add or override behaviors.

Why not inheritance?

  1. For a couple of reasons, it is a good idea to favor decorator over inheritance depending on what the use case is.
    You can add responsibilities to objects dynamically at runtime thanks to decorators without developing a new subclass. While using inheritance for every combination of improvements or alterations, there is a definition of a new subclass. The system may become more complex to comprehend and manage if this results in an explosion of subclasses.

  2. The open/Closed Principle states that software entities should be available for extension but closed for modification. Decorator follows this rule because the system can be expanded with new decorators without changing the present code while In the process of creating inheritance superclasses can be modified in order to make subclasses work as intended.

Implementation
Here, we are building a juice shop, where you can drink juice and also get the cost of the juice as shown in the interface there's a drink function and a cost function respectively.

public interface Juice {

     String drink();
     int cost();

}
Enter fullscreen mode Exit fullscreen mode

We are making an assumption that watermelon is the default juice in this shop. The watermelon juice is the concrete implementation of the juice interface as well as the MangoFlavour, OrangeFlavour, and PineAppleFlavour. Mango, Orange, and Pineapple take the juice object in as a constructor. which makes it easy for us to have mixed juice flavours and for them to wrap another juice object as a decorator.

public class WatermelonFlavour  implements Juice{

    @Override
    public String drink() {
        return "Watermelon flavour";
    }

    @Override
    public int cost() {
        return 5;
    }

}
Enter fullscreen mode Exit fullscreen mode
public class MangoFlavour implements Juice {
    Juice juice;

    public MangoFlavour(Juice juice) {
        this.juice = juice;
    }


    @Override
    public String drink() {
        return "mango " + juice.drink();
    }

    @Override
    public int cost() {
        return juice.cost() + 2;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class OrangeFlavour implements Juice {
    Juice juice;

    public OrangeFlavour(Juice juice) {
        this.juice = juice;
    }

    @Override
    public String drink() {
        return "orange " + juice.drink() ;
    }

    @Override
    public int cost() {
        return juice.cost() + 7;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class PineAppleFlavour implements Juice {
    Juice juice;

    public PineAppleFlavour(Juice juice) {
        this.juice = juice;
    }


    @Override
    public String drink() {
        return "pineapple " + juice.drink();
    }


    @Override
    public int cost() {
        return juice.cost() + 4;
    }
}
Enter fullscreen mode Exit fullscreen mode

Watermelon juice being the default flavour is initialized first and we can confirm that is the only flavour in the system, when we decide to decorate watermelon with mango we see that the juice contains two flavours and when we decorate it with orange we see that it contains three flavours up until the end.

  Juice juice = new WatermelonJuice();
        System.out.println("Drinking " + juice.drink() + " that costs $" + juice.cost());

        juice = new MangoFlavour(juice);
        System.out.println("Drinking " + juice.drink() + " that costs $" + juice.cost());

        juice = new OrangeFlavour(juice);
        System.out.println("Drinking " + juice.drink() + " that costs $" + juice.cost());

        juice = new PineAppleFlavour(juice);
        System.out.println("Drinking " + juice.drink() + " that costs $" + juice.cost());
Enter fullscreen mode Exit fullscreen mode

what this implementation and result shows is that we can provide additional functionality to an object without necessarily modifying the object which is one of the Solid principles;
An object should be open to extension but closed to modification.
The result of the decorating process shows us the flavour and the cost.

Drinking Watermelon flavour that costs $5
Drinking mango Watermelon flavour that costs $7
Drinking orange mango Watermelon flavour that costs $14
Drinking pine apple orange mango Watermelon flavour that costs $18
Enter fullscreen mode Exit fullscreen mode

Advantages

  1. You can add behaviors to objects dynamically without modifying their structure which promotes flexibility.
  2. Complies with open/close principle.

Disadvantages

  1. The system becomes complex because of the introduction of many classes.

Conclusion
Without changing the code of an object, the Decorator Pattern offers an elegant way of enhancing the object's behavior.
It adheres to the single responsibility principle by enforcing a single decorator per behavior and the open-close principle by wrapping an existing object without modifying it. Although it may add complexity, its advantages in terms of flexibility and modularity frequently make it a useful tool in a developer's toolbox.

Top comments (0)