This chapter covers the Decorator pattern.
GoF Definition
Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Concept
From the GoF definition, it is evident that this pattern uses an alternative to subclassing (i.e., inheritance). If inheritance is not allowed, how do you proceed? Yes, you guessed it right. It prescribes you to use composition instead of inheritance.
By following the SOLID principle, this pattern promotes the concept where your class is closed for modification but open for extension. (If you want to learn more about SOLID principles, go to https://en.wikipedia.org/wiki/SOLID_(object-oriented_design).) Using this pattern, you can add special functionality to a specific object without altering the underlying class.
A decorator is just like a wrapper (or topping) that surrounds the original object and adds additional functionality to it. This is why the Decorator pattern is also called a Wrapper pattern. This pattern is most effective when you add decorators dynamically. Since decorators are often added dynamically, it’s perfectly fine if you do not want them in a later phase of development, because the original object may still work.
Real-World Example
Suppose that you own a single-story house, and you decide to build an additional floor on top of it. You may not want to change the architecture of the ground floor, but you may want to employ a new design for the newly added floor that can fit on top of the existing architecture.
The case shown in Figure 7-3 is optional. You can use an existing decorator object to enhance the behavior, or you can create a new decorator object and add the new behavior to it. In step 2, you could also directly paint the original house. You don’t need to start painting once the new floor is added.
Computer-World Example
Suppose that you want to add border properties to a GUI-based toolkit. You could do this using inheritance, but that cannot be treated as an ultimate solution because you may not have absolute control over everything since the beginning. So, this technique is static by nature.
In this context, decorators can offer you a flexible approach. They promote the concept of dynamic choices. For example, you can wrap the component in another object (similar to Figures 7-2 and 7-3). The enclosing object is called a decorator, and it must conform to the interface of the component that it decorates. It forwards the requests to the original component and can perform additional operations before or after those requests. In fact, this concept allows you to add an unlimited number of responsibilities.
Implementation
In this example, five players are involved: AbstractHome, ConcreteHome, AbstractDecorator, FloorDecorator, and PaintDecorator.
Notice that AbstractDecorator holds a reference to AbstractHome. So, the concrete decorators (FloorDecorator or PaintDecorator in this example) are decorating an instance of AbstractHome.
You can see that FloorDecorator can add a floor (using the AddFloor() method), and when you use it, you must pay an additional $2500 for the additional construction. More importantly, before adding a floor, it calls the MakeHome() method of the AbstractHome class, which in turn calls the MakeHome() method from a concrete implementation of AbstractHome (i.e., ConcreteHome).
PaintDecorator acts similarly, but you have to pay more for it. (Yes, I assume that you are using luxurious paints for your home.)
Class Diagram
Solution Explorer View
Demonstration
Output
Q&A Session
7.1 Can you explain how composition promotes a dynamic behavior that inheritance cannot?
When a derived class inherits from a base class, it inherits the behavior of the base class at that time only. Though different subclasses can extend the base or parent class in different ways, this type of binding is known at compile time. So, the method is static. But by using the concept of composition, as in the previous example, you get dynamic behavior.
When you design a parent class, you may not have enough visibility about what kind of additional responsibilities your clients may want in some later phase. Since the constraint is that you cannot modify the existing code, in this case, object composition not only outclasses inheritance, but it also ensures that you are not introducing bugs in the old architecture.
Lastly, in this context, you must try to remember a key design principle that says classes should be open for extension but closed for modification.
7.2 What are the key advantages of using a decorator?
The existing structure is untouched, so you cannot introduce bugs there.
New functionalities can be easily added to an existing object.
You can not only add a behavior to an interface, but you can alter the behavior too.
You do not need to predict/implement all the supported functionalities at once (for example, in the initial design phase). You can develop incrementally. For example, you can add decorator objects one by one to support your needs. You must acknowledge that if you make a complex class first and then want to extend the functionalities, it will be a tedious process.
7.3 How is the overall design pattern different from inheritance ?
You can add, alter, or remove responsibilities by simply attaching or detaching decorators. But with simple inheritance techniques, you need to create new classes for new responsibilities. So, you may end up with a complex system.
Consider the example again. Suppose that you want to add a new floor, paint the house, and do some extra work. To fulfill this need, you can start with FloorDecorator because it is already providing the support to add a floor, and then use PaintDecorator to paint the house. Then you need to add a simple wrapper to complete those additional responsibilities.
Now you feel the heat of the “diamond effect” because in many programming languages, including C#, multiple base classes are not allowed.
You also discover that the inheritance mechanism is not only much more challenging and time-consuming compared to the Decorator pattern, but it may promote duplicate code in your application. Lastly, do not forget that inheritance promotes only compile-time binding (not dynamic binding).
7.4 Why are you creating a class with a single responsibility? You could make a subclass that can simply add a floor and then paint. In that case, you may end up with fewer subclasses. Is this correct?
If you are familiar with the SOLID principles, you know that there is a principle called single responsibility . The idea behind this principle is that each class should have responsibility for a single part of the functionality provided in the software. The Decorator pattern is effective when you use the single responsibility principle because you can simply add or remove responsibilities dynamically.
7.5 What are the disadvantages associated with this pattern?
I believe that if you are careful, there are no significant disadvantages. But if you create too many decorators in the system, it will be hard to maintain and debug. So, in that case, they can create unnecessary confusion.
7.6 In the example, the AbstractDecorator class is abstract, but there is no abstract method in it. How is this possible?
In C#, a class can be abstract without containing an abstract method, but the reverse is not true. In other words, if a class contains at least one abstract method, it means that the class is incomplete, and you are forced to mark it with the abstract keyword.
So, in this example, you cannot simply instantiate an AbstractDecorator instance, because it is marked with the abstract keyword.
7.7 Are decorators used for dynamic binding only?
No. You can use the concept for both static and dynamic binding. But dynamic binding is its strength, so I concentrated on that here. The GoF definition also focuses on dynamic binding only.
The I/O streams implementations in the .NET Framework, .NET Core, and Java use the Decorator pattern. For example, the BufferedStream class inherits from the Stream class. Note the presence of two overloaded constructors in this class; each of them takes a Stream (Parent class) as a parameter (just like demonstration 1). When you see this kind of construct, there is a possibility that you are seeing an example of the Decorator pattern. BufferedStream is acting like a decorator in .NET.