Key Words: Decorator pattern, inheritance misuse
Topics at a glance:
- Let us decorate objects
- Inheritance misuse
- Decorating windows on screen!
Decorator pattern
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.
Inheritance misuse
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | #include <iostream> #include <memory> using namespace std; class Abstract_Window { public: virtual ~Abstract_Window() {} virtual void draw() = 0; virtual void close() =0; }; // some utility functions int minimize_object(Abstract_Window *abs_window) { // do something to minimize this window return 0; } int restore_object(Abstract_Window *abs_window) { // do something to restore this window return 0; } class Basic_Window : public Abstract_Window { private: int x_; int y_; float width_; float height_; public: Basic_Window(int x = 0, int y = 0, float width = 5, float height = 2.5) : x_{x}, y_{y}, width_{width}, height_{height} { cout << "Basic_Window instance @ (" << x_ << ", " << y_ << ")" << " with width " << width_ << " and height " << height_ << " is created " << endl; } ~Basic_Window() { cout << "Basic_Window has been destroyed" << endl; } void draw() { cout << "Window with [X]" << endl; } void close() { cout << "Basic_Window has been closed" << endl; } }; class Window_Decorator : public Abstract_Window { private: unique_ptr<Abstract_Window> abs_window; public: Window_Decorator(Abstract_Window *this_window) { abs_window.reset(std::move(this_window)); } virtual ~Window_Decorator() { abs_window.reset(); } void draw() { cout << "Window with [-]" << endl; abs_window->draw(); } void close() { abs_window->close(); } }; class Minimizable_Window : public Window_Decorator { private: Abstract_Window* abs_window_; public: Minimizable_Window(Abstract_Window *abs_window) : Window_Decorator(abs_window), abs_window_{abs_window} { } void draw() { Window_Decorator::draw(); } void close() { Window_Decorator::close(); } void minimize() { if(0 == minimize_object(abs_window_)) { cout << "Window minimized" << endl; } } void restore() { if(0 == restore_object(abs_window_)) { cout << "Window restored" << endl; } } ~Minimizable_Window() { cout << "Minimizable_Window destroyed" << endl; } }; class Window_Guard { public: enum class window_types{basic, minimizable}; enum class window_operations{draw, close, minimize, restore}; private: unique_ptr<Abstract_Window> window; bool active_window; window_types type_; public: Window_Guard( window_types type = window_types::basic, int x = 0, int y = 0, float width = 5.0F, float height = 2.5F ) : window{nullptr}, active_window{false}, type_{type} { switch(type) { case window_types::basic: { window.reset(new Basic_Window(x, y, width, height)); break; } case window_types::minimizable: { window.reset(new Minimizable_Window(new Basic_Window(x, y, width, height))); break; } } active_window = true; } int do_this(window_operations op) { int ret = -1; if(true == active_window) { switch (op) { case window_operations::draw: { window->draw(); ret = 0; break; } case window_operations::close: { window->close(); ret = 0; // destroy the window, as it is closed active_window = false; window.reset(); break; } case window_operations::minimize: { ret = -2; // unsupported operation if(type_ == window_types::minimizable) { // use dynamic cast auto window_ptr = dynamic_cast<Minimizable_Window*>(window.get()); if(window_ptr != nullptr) { window_ptr->minimize(); ret = 0; } break; } } case window_operations::restore: { ret = -2; // unsupported operation if(type_ == window_types::minimizable) { // use dynamic cast auto window_ptr = dynamic_cast<Minimizable_Window*>(window.get()); if(window_ptr != nullptr) { window_ptr->restore(); ret = 0; } } } default: { // do nothing } } // switch ends } return ret; } ~Window_Guard() { // destroy the window if(active_window == true) { window.reset(); } } }; int main() { // create a Minimizable_Window Window_Guard wg_1(Window_Guard::window_types::minimizable); // use default parameters wg_1.do_this(Window_Guard::window_operations::draw); wg_1.do_this(Window_Guard::window_operations::minimize); wg_1.do_this(Window_Guard::window_operations::restore); wg_1.do_this(Window_Guard::window_operations::close); cout << "Main Ends here" << endl; return 0; } |
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 😊