The Decorator Pattern
Decorator pattern attaches additional responsibilities to an object dynamically. Decorator pattern provides a flexible alternative way to subclassing for extending functionality.
Let’s understand the decorator pattern with a quite popular example of coffee maker. Let’s name it “Moon Cafe”. Moon Cafe started its operation and introduced four coffee types initially called “House Blend”, “Dark Roast”, “Decaf”, and “Espresso”. Moon Cafe approached the designer to come up with a suitable design to solve the given problem.
The designer was pleased and thought of a solution and came with the following design solution:
It looks good. What do you say?
Now, let’s say there is a requirement to add condiments like mocha, whip etc. and Moon Cafe wants to sell beverages like House Blend with Mocha, Decaf with Whip or Espresso with Double Mocha etc. What would be solution for this?
The designer gave a thought and came up with the following solution:
It looks good at first glance but have you noticed that there will be a class explosion if there is a requirement to add n number of different types of coffees. Can you think of the design principles that the designer didn’t consider?
The designer didn’t consider the two of the discussed design principles in previous articles.
Let’s consider more possible future cases. Here they are:
- Change in price of condiment
- Addition of new condiment
- Addition of new beverage
- Addition of combination of various condiments to the existing coffee
In the mentioned scenarios, can you imagine the code changes that would take place with proposed design. I can think of massive changes that would not be maintainable. What would be solution?
There is a design principle which is applicable just for this situation.
Design Principle [Open-Closed Principle]
Software entities(classed, modules, or functions) should be open for extension, but closed for modification.
Now, think of the problem at hand again. Can you imagine the solution like this
You can see that HouseBlend coffee is decorated with Whip followed by Mocha. When we calculate the cost, it will calculate the cost of HouseBlend first, followed by Whip. The total cost would be $1.5 + $0.5 + $1.0 = $3.0.
If you see closely, you will see that any type of concrete coffee can be decorated with any number of condiments. Congratulations! You just learned the decorator pattern.
The designer got excited and came up with the final design solution
You can see that we have one Beverage abstract class which will be extended by concrete coffee classes like HouseBlend, DarkRoasted, and Decaf. There is one more abstract class called BeverageDecorator which will extend Beverage class and will have additional instance of Beverage class to wrap the concrete class. Please note that parent object type of the concrete classes and condiment classes are same. This is a requirement to match the wrapping and wrapped classes in the first place.
You might wonder that we have used inheritance. What is different here? If you observe closely, you will find that we are adding behaviour to our existing coffees dynamically by not using inheritance but by using composition and delegation. Inheritance is primarily used to match the parent type of the component and decoration objects.
Decorator pattern attaches additional responsibilities to an object dynamically. Decorator pattern provides a flexible alternative way to subclassing for extending functionality.