Overview
Today our purpose is to know why and when to use the decorator pattern. In the previous post about class explosion, we found that there is class explosion if we go with the first trial. Now, what we can do to improve this design is introduce one abstract super-class Food which will be specialized by Pizza and Sandwich classes.
Revised Class Design
The Food class will be abstract and serve as the base for specialized implementations like ItalianPizza. When you create an instance of ItalianPizza, you can set properties like jalapeno and olives to true or false according to the toppings you want. Using the same properties, you can calculate the cost of the pizza.
Issues With This Approach
However, there are significant issues with this approach:
- What if a new item is introduced for toppings? For example, corn. This leads us to modify the code – a violation of the Open-Closed Principle. Every time a new topping is needed, the class must be modified.
- What if double cheese is required? There is a lack of customization at runtime. You need to add a new class for this variant. It could also be more olives (or triple cheese). This creates exponential growth in the number of classes for every combination.
The Problem: Compile-Time Binding
The fundamental problem in the above design lies in the compile-time binding of all pizza ingredients with the Pizza class. What we really want is flexibility at runtime so that we can add or remove any behavior or property to an object dynamically.
The Decorator pattern, explained in the next post, comes to the rescue by providing this runtime flexibility and overcoming the limitations of compile-time binding.
Summary
The issues identified above highlight why the Decorator pattern is necessary. Instead of creating classes for every possible combination of toppings, the decorator pattern allows you to dynamically add functionality to objects at runtime, maintaining clean code and adhering to design principles like the Open-Closed Principle.