Software Design Patterns: Decorator in a Nutshell
Let us talk about structural design patterns. Patterns help us to connect classes and objects in order to form larger structures. Decorator is one pattern in the structural category.
The Decorator pattern allows extending (decorating) the functionality of an object. It is an alternative to inheritance. Decorator provides new functionality in run-time to a specific instance, while inheritance adds functionality at compilation time and the change affects all instances of the new class.
For example, from a class Person, we can extend a class Student which adds functionality to Person, and then we can choose to create instances of Person (the simple one) or Student (the one with something extra):
Person javier = new Person();
Student mary = new Student();
The key idea of a decorator is to achieve a similar result, but with an approach that in code could be as follows:
Person javier = new Person();
StudentCredential studentCredential = new StudentCredential();
We are decorating a Person instance by adding something that was not there before. Instead of applying inheritance and adding things to the new class, we create a new class, totally independent, that has whatever new characteristics or behaviors we could want to add to another class.
When to use inheritance and when to use a Decorator pattern? Well, one of the most important factors to consider is whether or not polymorphic behavior is needed. If you don’t need polymorphism, you probably should not be using inheritance. 🤔
Now, let us improve our approach with another feature. Imagine a system with a class Ball and a class Box. We would like to use a Box object to decorate a Ball object.
This sounds similar to the example with Person and StudentCredential. Easy to implement. What if we would like to put the box with the ball inside of another box, or put a ball and a box together with another ball inside of another box, or any number of other combinations you can imagine.
Does this sound familiar? As shown in Figure 1, just think about our class Ball as the class JButton (in the Java API), and our class Box as the class JPanel (in the Java API). Panels and widgets (JButton, JLabel, and others) in Java work with the decoration paradigm. Widgets are the base elements. The source code would look like this:
JPanel p1 = new JPanel();
JPanel p2 = new JPanel();
JPanel p3 = new JPanel();
JButton b1 = new JButton ("one");
JButton b2 = new JButton ("two");
As with many other patterns, the story begins with an interface. This interface will be a common parent for decorators and items. Let us call our items components, i.e., create a class Component. Our class Ball is a specialization of Component; and our class Box a specialization of Decorator. And, we can add more, such as a class Bag that is a specialization of Decorator too. We are using inheritance because we will need polymorphism. 🙂
Our family of classes is shown in Figure 2. The core shown in yellow color includes the interface that is the parent of our top classes Component and Decorator. And, our examples of Component and Decorator specializations (Ball, Box, and Bag) are shown in blue color.
Only one element to add and our work is complete: a relationship of aggregation between Decorator and ComponentInterface. This is the core of the solution. We are defining that “a Decorator can have a ComponentInterface” (aggregation). Therefore, “a Decorator can have a Component” or “a Decorator can have another Decorator” and “the new Decorator can have a Component or an additional Decorator”, and so on. Then, just because of the magic of polymorphism:
- a Box can have a Ball;
- a Box can have a Box, which can have a Ball;
- a Box can be empty (since this is aggregation, not composition);
Moreover, just by adding multiplicity of * to the aggregation relationship, it is possible to define:
- a Box can have one or more Ball instances; and,
- a Box can have one or more Box instances, which can have one or more Ball instances each.
The method operation() as well as the specific variables in the class Box or the specific methods in the class Bag, are just examples. They can be named and customized as needed. Let us review that in the source code of an example.
The Decorator pattern is an especially interesting alternative to multiple inheritances. Imagine a class Person that specializes in a class Teacher and a class Student. Then we want to create a class TeachingAssistant inheriting from Teacher and Student since a TeachingAssistant is a Student hired to teach. We have created a circular dependency.
Some languages, such as Java, will not allow multiple inheritance, while others could be susceptible to the diamond problem. Using decoration, the solution is as simple as adding two decorators (probably TeachingCredentials and StudentCredentials) to the base class (component) Person.
Let us do something similar. We are developing a Companion (a chatbot, virtual agent, or similar) and we would like to define diverse types of companions including a companion that provides help (a HelperCompanion) and a companion that provides emotional support (an AffectiveCompanion).
The solution looks straightforward using inheritance. But, the situation gets problematic if we would like to have a companion that at the same time is both affective and a helper. A decorator solves the situation with an elegant solution that separates the core of a companion and its diverse personalities. Figure 3 shows the design for the solution. The source code is as shown in the following paragraphs.
First the interface — the top of our hierarchy. All our companions have a behavior defined in a method named doSomething().
Two classes implement the interface; therefore, the family splits into a branch for the core items and a branch for the possible decorations. In our example, BasicCompanion is the class for the core entity and CompanionDecorator is our common parent for the decorators. Our BasicCompanion is a companion in which behavior is limited to a greeting of “Hello!”
In CompanionDecorator, we have 3 important elements:
- the attribute Companion (the one fulfilling the aggregation relationship);
- a method add() that will help us to provide a value to the attribute later; and,
- the override for the interface method — key idea, the action in the decorator calls the action in the item being decorated. Our method doSomething() in CompanionDecorator calls the doSomething() method in any item (Companion object) being decorated.
Finally, CompanionDecorator specializes in HelperCompanion and AffectiveCompanion. Notice that the hard work has been done in the parent class — the children only need to override the doSomething() method adding their own behaviors. HelperCompanion prints “I am here to help you” and AffectiveCompanion prints “I am here to cheer you”.
Putting the pieces together, we can create companions as shown in our class Main in Figure 9.
And the output will be something like this:
Hello! I am here to help you.
Hello! I am here to cheer you. I am here to help you.
Nothing fancy, but you got the idea. Other important creational patterns include Adapter, Bridge, Composite, and Proxy. They are each material for another story. Hope you enjoy reading. Feel free to leave your comments or questions below.