Nowadays, optionality is everywhere. Typically, before purchasing an item, one can choose among multiple configurations, models, and kinds available.
The decorator pattern will allow you to implement a dynamic code structure where the new behaviors can be applied to the basic object.
It increases your optionality.
Pattern introduction
The decorator pattern allows you to apply new behavior to the objects in a dynamic way.
Let’s rephrase the definition of this pattern, based on the famous GoF book:
Decorator pattern – allows to dynamically apply new behavior to the object. Decorators give you flexibility similar to inheritance, offering in return a much more extended functionality.
A decorator is a serious alternative for inheritance. With the composition and delegation, it allows adding new responsibilities in a runtime.
The decorator is widely used in Java standard library, you can find it in the java.io streams (InputStream, OutputStream, Reader, Writer, etc.) and in java.util.Collections.
Open-closed principle
The decorator pattern is following the open-closed principle (OCP). It’s one of the good design rules in object-oriented programming.
Let’s rephrase its definition.
Classes should be open for extension but closed for modifications.
(Open-Closed Principle)
What does it exactly mean in our case? We’re going to use composition instead of inheritance.
Extending an object’s behavior can be done in runtime (instead of statically setting the behavior as it is for the inheritance). The original code of the parent class will remain intact but the expected outcome will change. No parent class modification means that there will be no room for new bugs introduction or possible side-effects.
Is that definition inconsistent? Of course, not. There are OOP techniques that allow us to extend the existing functionalities without the direct modification of code
Remember what the Observer pattern has allowed us to do? By adding new observer objects we could extend the observable object without the need to add new code to the observable class.
Please note that it’s not a golden rule. Following OCP everywhere can lead to complex and hard-to-follow code creation.
Example – coffee shop
Let’s take a coffee shop as an example for the demonstration of the decorator pattern.
(Non) caffeinated drinks are typically available in different configurations – with or without additives.
Our goal is to provide the functionality that allows us to order the drinks. It should calculate the overall cost of the drink and provide a meaningful description of the drink with its all additives.
Classes
Let’s take a look at the classes we’re going to use in this example:
As we can see, we have:
- Drink – this is the component abstract class interface. It has the cost method which will be used to cost calculation and the description property. It has the following subclasses:
- Coffee, NonCaffeine – implementing respective methods setting fixed price and descriptions.
- AdditiveDecorator – this is the decorator abstract class that inherits the Drink class. It has the following subclasses:
- Milk, Cinnamon – similarly to Coffee and NonCaffeine, additives are also overriding the methods adding their own costs at the top of the passed newDrink drink cost.
Additionally, they have withDrink factory methods that will help us increase the readability of code. See them in action in the test cases below.
- Milk, Cinnamon – similarly to Coffee and NonCaffeine, additives are also overriding the methods adding their own costs at the top of the passed newDrink drink cost.
The decorator in action – test
The mechanics of the decorator pattern can be easily seen in action in the example below.
Let’s take a look at the simple test cases:
Introduction of withDrink
factory methods are not a part of a standard decorator pattern implementation but it allows us to increase the readability of the code.
The standard, wrapping approach to decorated object creation (like Cinnamon(Milk(Coffee())))
) is not very elegant. Imagine creating very complex multiple decorated objects this way.
As always you can try the code yourself, it’s available on GitHub.
Conclusion
If you’re looking for a way to create flexible and configurable objects then the decorator pattern is your way to go.
Use it carefully though because overusing it can increase the overall complexity of the code (too many wrappers and small classes in the codebase).