Topics at a glance:
- Let us decorate objects
- Inheritance misuse
- Decorating windows on screen!
In this chapter we will see our second structural pattern – decorator pattern.
To explain this pattern, as usual I will take a problem scenario, where one requires to add additional functionalities on an existing class. Say, I have a Page class. When draw() method is invoked on a Page instance, it will draw a blank page. Now, if we need a page with left margin, then we will inherit a derived class, say Page_With_Left_Margin from Page class and override the method draw() and add a left margin on the page when drawn. This will be done by invoking the base Page class’s draw() method and then putting a left margin on the drawn page. This look normal and feasible.
Now, in course of design a requirement is added to draw a page with lines and a left margin. In normal course we will again derive another class, but this time from Page_With_Left_Margin and then repeat the steps as described above to add lines also on top of a page with a left margin. We can call this class as Page_With_Left_Margin_And_Lines
Now, one more requirement is added to draw a page, but with just lines and not with any left margin. Whether we will derive another class say, Page_With_Lines, from base class Page.
The above design example leads to a complex class hierarchy and is exceedingly difficult to maintain. This is an example where there is misuse of inheritance, or one can say that inheritance is running amok!
Another ideal example can be a Window class, which could be drawn on the screen and also has an interface to close. Suppose we need another Window class with an extra functionality to minimize as well. i.e. when this special window instance is drawn, it should have buttons for close [X] and to minimize [-]. If we design this example as the one we described in case of Page class, we will end up in the same mess because of inheritance misuse.
To avoid this issue, we have come up with the decorator pattern. Decorator pattern is somewhat like composite pattern in some ways. In composite pattern we created an abstract base class and two derived classes. One primitive class and another composite class. Here, also we should start with an abstract base class, say, Abstract_Window class, and derive at least two classes. One normal class say, Basic_Window class, and a decorator class called Window_Decorator.
Window_Decorator is a wrapper class, that its wraps an instance of abstract class, similar to the way a composite class containing instances of its abstract base class. For any sort of additions to the functionalities, in future, programmer must derive from this decorator class and do the necessary additions. Decorator class works on the principle of functionality delegation. Means, in case of a polymorphic method (i.e. member functions marked as virtual) any derived class of decorator class, should invoke its base class’s ( i.e. decorator class) method as well. Decorator in turn, calls the wrapped instance’s method.
This is the philosophy behind Decorator pattern.
- Decorator pattern avoids complex inheritance that is difficult to maintain, by keeping the base concrete class separate from the decorator class and its sub-classes.
- Decorator pattern works on the principle of functionality delegation.
Now, let us see all this in action with the example of a Window class system, as discussed above:
You might have noticed an extra thing – a Window_Guard class. Window_Guard just like a Thread_Guard and Lock_Guard makes sure the guarded object is cleaned up properly.
The functionalities of Window_Guards are as follows:
1. It hides the creation of window object in its constructor
2. It takes the sole responsibility of destroying the window properly and thus cleaning the acquired resources
a. It destroys the window through its destructor when Window_Guard itself goes out of scope
b. It destroys the window when user closes the window object. Any operation further on the window will result in a failure.
3. It also makes sure the invoked operation is valid for the instance of the window object that it guards.
a. If any bug in the program initiates a minimize operation on a Basic window instance, this is treated as an invalid operation
b. If any bug in the program initiates a restore operation on a Basic window instance, this is treated as an invalid operation
Now, let us see the result:
Basic_Window instance @ (0, 0) with width 5 and height 2.5 is created Window with [-] Window with [X] Window minimized Window restored Basic_Window has been closed Minimizable_Window destroyed Basic_Window has been destroyed Main Ends here
Through decorator pattern’s functionality delegation, a basic window object is created here and is decorated with a minimize/restore functionality as well. Window guard properly takes care of the life cycle management of the window object it guards.
Enjoyed the chapter? Let me know in the comments below. Thanks 😊