Chapter 16 : Series On Design Patterns – Decorator Pattern

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 patterndecorator 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.

  1. 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.
  2. 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 😊

0

Leave a Reply