Decorator Pattern | Recursively adding responsibilities at runtime
Definition
The Decorator Pattern attaches additional responsibilities to an object dynamically at the runtime. It provides a flexible alternative to subclassing for extending functionality.
Introduction
As the definition above states that the Decorator Pattern adds additional features/responsibilities on top of your base class at the runtime. We don’t have to perform any changes in the source code to add any responsibilities, everything happens completely dynamically at the run-time.
Before moving on to the Decorator Pattern, let's first understand why we need such a pattern? What are the situations in which this pattern should be preferred?
Let’s understand this with the example of Burger King which has recently introduced a variety of burgers to choose from. Currently they have three base burgers i.e. Veg, Chicken and Egg along with multiple add-ons like Cheese, Chips, Veggies and Sauce. As a customer now I can combine my base burger with these add-ons to create my own burger meal preparation. For example: I can have a “Chicken burger with cheese and sauce”.
Initially the class design looks like this.
In the above design we have an abstract class named Meal which is subclassed by all the different Burger classes. Now, every Burger class has their own getPrice() method because every base burger and add-ons have different prices.
The superclass Meal looks like this.
One of the burger meal class ChickenBurgerWithCheeseAndSauce will look like this.
In this way every preparation of a burger meal will have its own cost depending upon the base burger and the combination of add-ons.
The above design will lead to a Class Explosion. As there can be a lot of different combinations of Burger meals and hence a huge number of classes for every meal. The engineers at Burger King will have to maintain a huge number of different classes for different burger meals and might also have to add a lot more classes even if one more add-on is introduced to their meal catalogue.
This is very complex and almost impossible to maintain or extend.
Alternate Approach
Let’s discuss yet another alternative approach which can get us rid of the Class Explosion issue faced in our previous design.
In this design, instead of referring to every burger meal preparation as a separate class we can add a boolean flag for every add-ons in our super class and set them true/false depending upon whether that particular add-on is included in our burger meal or not.
Our class design will look like this.
The class implementation of the Meal superclass looks like this.
The base burger class VegBurger will inherit from the superclass Meal and will look like this.
If a user wants to order a “Chicken burger with cheese and sauce”, it can be done this way.
The above design avoids Class Explosion. We no longer have to maintain classes for every single burger meal preparation. But this design violates the Open-Closed Principle. The principle states that a class should be open for extension, but closed for modification. But in our design even if we have to add an extra Add-on, say “Mint Sauce”, we have to change the code in our Meal base class.
Recommended by LinkedIn
Any new add-on will force us to add new methods and alter the existing getDescription() and getPrice() methods in the superclass (Meal).
Decorator Pattern
We discussed the previous implementations and each of them had some potential design flaws. Decorator Pattern allows us to add requirements to the base class dynamically at the runtime.
In our example of Burger King we can dynamically put add-ons over the base burger the way we like. For this we neither have to deal with the class explosion nor have to change the class implementation in case of any modification/addition.
The Decorator design pattern allows the current design to be extendable to add more add-ons if required. It allows the add-on classes to wrap over the base class and recursively calculate the price or description of the meal preparation.
The class design looks like this.
Our Meal superclass looks like this.
Our base burger ChickenBurger class extends the Meal class and looks like this.
Our AddOn class extends the Meal class and looks like this.
An add-on Chips class looks like this.
In the above class design we can see that the add-on Cheese class wraps a Meal class. In this way we can wrap add-ons over a prepared meal. Hence if a user wants a “Chicken burger with cheese, veggies and chips” then they can add them dynamically at runtime in this way.
As we wrap the add-ons over the prepared meal, the final meal gets its price and description by recursively calling all the wrapped meals.
The wrapped class design looks like this.
The process of computation of Price recursively for the order “Chicken burger with cheese, veggies and chips” looks like this.
The similar method applies for computing the Description of a prepared meal.
Conclusion
We discussed the Decorator Pattern in detail along with the initial implementation which caused Class Explosion. We also discussed yet another alternative implementation that violated the Open Closed design principle. We finally solved the Burger King design problem effectively with the Decorator Pattern.
You can find the sample code for all the implementations discussed in this edition on my Github repo. Feel free to check it out!
http://lldcoding.com
amazing article sir keep posting up
My LLD Github repo: https://github.com/SauravP97/Saurav-Low-Level-Design-Template