Chapter 22 : Data Structures With C++ – Hash Tables

Key Words: Hash tables, hash function, hash buckets, string hash, PJW algorithm to find string hashes.

Topics at a glance:

  • Understanding Hash Tables, hash functions, hash buckets
  • How to implement a custom Hash table that can store integers and strings.

In this chapter we’ll see another popular data structure known as the “Hash Table”. The core philosophy behind hash table is, as the name suggests, a unique hash key, associated with each of the values entered in the table. So how values and keys are associated? A hash table uses a unique key for identifying the storage location of the data it stores. Hash table finds the key dynamically and will not generally stores the key itself. Whenever a new data has to be entered, then and there a key is determined and using this key, a location will be decided for storing this data. When we want to search or delete this value/data then also hash table determines the key and finds the location and manipulates the data. That means, hash table necessarily do not require to store this key-value mapping information internally.

The limitation of a Hash table can be related to the limitation of the hashing algorithm itself. Hashing algorithm can often generates the same key for different values and will lead to a hash collision. To avoid this there are two popular methods:

  1. Chain hashing
  2. Open address hashing

In this chapter we’ll see what’s chain hashing. Chain hashing is relatively less complex than open address hashing. If it requires to frequently add and delete keys/values from the hash table, then it is better to use chain hashing. Also, it is safe from the issue of clustering that is inherent with open address hashing algorithm. The main disadvantage of chain hashing is its poor cache performance and inefficient space utilization.

We will adopt a technique using hash buckets for illustrating a hash table implementation using chain hashing. Also, to avoid the issue with inefficient space utilization, we’ll use std::lists rather than fixed size arrays to implement the storage strategy. ‘std::lists‘ are more efficient than std::vectors when it requires frequent insertion and deletion.

In our implementation, we will use a custom hash function that generates a hash key, which in turn, will be used as the bucket identifier. Storage location is an array of lists. The key computed by the hashing function will be the index to this array of lists. Data will be pushed back into a list found at an index. Here, key itself is not the location in the list. Also, it doesn’t make any sense to use indexing operation on a list which is not supported in C++ anyway. Instead of storing data in a single list that grows with every new data added, we use buckets of lists. In each bucket, a smaller list will be present and will be relatively faster to search and find the data from a smaller list.

Now, coming to custom hashing function. For integer values, we will use value modulo bucket size as the key computation algorithm. We’ll support one more datatype in our list. i.e. strings. For strings we will use the PJW hash function, used in Unix ELF formats.

Please refer to Wikipedia link:

https://en.wikipedia.org/wiki/PJW_hash_function for more information on PJW hash function.

Now, let us see the implementation of our custom Hash table.

  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
#include <iostream>
#include <list>
#include <string>

using namespace std;

template <typename T, int N>
class Hash_Table
{
public:
    enum class error_codes{invalid_key_type = 0, invalid_key = 1};
private:
    list<T> h_tables[N];
    string hash_type;
    
    int hash_key(T val)
    {
        int key = -1;
        unsigned int h = 0U;
        unsigned int g = 0U;
        
        if(hash_type.compare("integer hash") == 0)
        {
            int *temp_val_ptr = reinterpret_cast<int *>(&val);
            int temp_val = *temp_val_ptr;
            if(temp_val < 0){temp_val *= -1;}
            key = temp_val % N;
        }
        else
        {
            // string hash uses PJW hash algorithm 
            // in UNIX ELF string hashing 
            string *temp = reinterpret_cast<string*>(&val);
            char *str = const_cast<char *>(temp->c_str());
            char *p;
            int int_eq = 0;
            
            for(p = str; *p; ++p)
            {
                int_eq = static_cast<int>(*p);
                h = (h << 4) + int_eq; 
                g = h & 0xF0000000U;
                
                if(g != 0x0U) // i.e. high nibble is set 
                {
                    h ^= (g >> 24);
                }
                
                h &= ~g;
            }
            
            key = static_cast<int>(h) % N;
        }
        
        return key;
        
    }
    
public:
    Hash_Table()
    {
        hash_type = "integer hash";
        constexpr bool integer_type = is_same<T, int>::value;
        constexpr bool string_type = is_same<T, string>::value;
        
        // check for key type. class invariance 
        if(   (integer_type == false) 
            &&(string_type == false) )
        {
            cout << "invalid key type" << endl;
            throw error_codes::invalid_key_type;
        }
        
        if(string_type == true)
        {
            hash_type = "string hash";
        }
        
        cout << hash_type << " has been created" << endl;
    }
    
    ~Hash_Table()
    {
        cout << hash_type << " has been destroyed" << endl;
    }
    
    int insert_value(T val)
    {
        int status = -1;
        int key = hash_key(val);
        
        if(key != -1)
        {
            // found a valid key
            h_tables[key].push_back(val);
            status = 0;
        }
        
        return status;
        
    }
    
    int delete_value(T val)
    {
        int status = -1;
        int key = hash_key(val);
        
        if(key != -1)
        {
            auto itr_start = h_tables[key].begin();
            auto itr_end = h_tables[key].end();
            
            for(auto itr = itr_start; itr != itr_end; ++itr)
            {
                if(*itr == val)
                {
                    // found the val
                    h_tables[key].erase(itr);
                    status = 0;
                    break;
                }
            }
        }
        
        return status;
    }
};

You can see that Hash_Table class is a template class. I have defined the class invariant that the data type should be either an integer data type or a string data type. Any other data type will throw an exception from the constructor.

Let us see the result of executing the following code:

 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
int main()
{
    int unsorted_array[] = {5, 2, 6, 7, 1, 10, 11, 4, 8};
    int status = 0;
    
    cout << "\nTesting integer hash..." << endl;
    Hash_Table<int, 5> ht_1;
    
    for (auto val : unsorted_array)
    {
        status = ht_1.insert_value(val);
        if(status == 0)
        {
            cout << "Val : " << val << " added into hash table" << endl;
        }
        else
        {
            cout << "Val : " << val << " not added into hash table" << endl;
        }
    }
    
    cout << "\nTesting deletion..." << endl;
    //delete 2
    if( 0 == ht_1.delete_value(2))
    {
        cout << "2 has been deleted from hash table" << endl;
    }
    else
    {
        cout << "2 not deleted from hash table" << endl;
    }
    
    //delete 10
    if( 0 == ht_1.delete_value(10))
    {
        cout << "10 has been deleted from hash table" << endl;
    }
    else
    {
        cout << "10 not deleted from hash table" << endl;
    }
    
    //delete 10 again
    if( 0 == ht_1.delete_value(10))
    {
        cout << "10 has been deleted from hash table" << endl;
    }
    else
    {
        cout << "10 not deleted from hash table" << endl;
    }
    
    // Now testing string hash
    cout << "\nTesting string hash..." << endl;
    Hash_Table<string, 5> ht_2;
    
    if(0 == ht_2.insert_value("Hello_String_1"))
    {
        cout << "Hello_String_1 added into Hash_Table" << endl;
    }
    else
    {
        cout << "Hello_String_1 is not added into Hash_Table" << endl;
    }
    
    if(0 ==    ht_2.insert_value("Hello_Another_String_1"))
    {
        cout << "Hello_Another_String_1 is added into Hash_Table" << endl;
    }
    else
    {
        cout << "Hello_Another_String_1 is deleted from Hash_Table" << endl;
    }
    
    cout << "\nTesting deletion..." << endl;
    
    // delete both strings 
    if(0 == ht_2.delete_value("Hello_String_1"))
    {
        cout << "Hello_String_1 is deleted from Hash_Table" << endl;
    }
    else
    {
        cout << "Hello_String_1 is not deleted from Hash_Table" << endl;
    }
    
    if(0 ==    ht_2.delete_value("Hello_Another_String_1"))
    {
        cout << "Hello_Another_String_1 is deleted from Hash_Table " << endl;
    }
    else
    {
        cout << "Hello_Another_String_1 is not deleted from Hash_Table" << endl;
    }
    
    cout << "\nMain ends" << endl;
    
    return 0;
}

Result:

Testing integer hash...
integer hash has been created
Val : 5 added into hash table
Val : 2 added into hash table
Val : 6 added into hash table
Val : 7 added into hash table
Val : 1 added into hash table
Val : 10 added into hash table
Val : 11 added into hash table
Val : 4 added into hash table
Val : 8 added into hash table

Testing deletion...
2 has been deleted from hash table
10 has been deleted from hash table
10 not deleted from hash table

Testing string hash...
string hash has been created
Hello_String_1 added into Hash_Table
Hello_Another_String_1 is added into Hash_Table

Testing deletion...
Hello_String_1 is deleted from Hash_Table
Hello_Another_String_1 is deleted from Hash_Table

Main ends
string hash has been destroyed
integer hash has been destroyed

See how our Hash table works for both integer and strings? It is possible to further improvise our Hash_Table class to support unsigned integers and floats as well. Also, we can add functions to print the data stored in the hash table. I’ll let you guys to look into those aspects of improvisation.

For now, we can use this Hash_Table implementation for our understanding purposes.

Enjoyed the chapter? Let me know in the comments below. Thanks! 😊

0

Chapter 21 : Data Structures With C++ – Binary Search Tree

Key Words : Binary Search Tree in C++, deleting nodes in Binary Search Tree.

Topics at a glance:

  • The simplicity of linear data structures – revisiting our custom Array, Queue
  • The algorithmic efficiency of non-linear data structures
  • Understanding Binary Search Tree (BST) and how to implement one in C++
  • What all things to consider in deleting a node in a BST

I am starting chapters on data structures with Binary Search Tree (BST). In fact, I have already discussed some other data structures in previous chapters. For instance, thread-safe queues with our Queue class discussed in chapter 11 on threads and again in our logger implementation discussed in chapter 18.  Also, our own implementation of a safe Array class in chapter 8. If we add a custom allocator to our Array class, it will become a custom Vector. As our Array class already has (random access) iterator support, adding an allocator will make it a considerably basic version of std::vector. The main difference between our custom Array class and std::vector is that Array once allocated cannot dynamically shrink or expand its capacity, as it doesn’t have memory allocator functionality, that a std:vector has.

Queues, arrays, and vectors are examples for linear data structures. i.e. the traversal always happens in a linear fashion from one index location to another index location. The generalization of all such data structures can be done on the basis that they all store data in consecutive memory locations. We also have data structures, that stores data in locations that are not necessarily consecutive always. These data structures fall under the category of non-linear data structures. One example is Binary Search Tree (BST). The question is why we need non-linear data structures? Simply because the complexity in sorting, searching, or finding any data in a linear data structure is always O(n), where ‘n’ is the number of data it has stored, or worse. Whereas in non-linear data structures, it is considerably less. Say in case of BST, for sorting, the algorithm complexity is just O(log n).

Binary Search Tree in C++

Binary search tree stores data in nodes. Each node can have a maximum of two children; One left child and one right child. Any BST starts with just one node called the root node. If the data to be entered is less than the root node data, a new node will get created and will be assigned as the root node’s left child. This data will be added to that new node. Similarly, any data that is higher than the root node will be added to a new node created on the right side of the root node. Each of these child nodes, left and right, are themselves a sub-BST and should comply with the same rules for adding new data to their child nodes. This is the philosophy behind BST. There is one more thing. You cannot have duplicate data stored into a BST. By ‘data’, I refer to the unique keys used for uniquely identifying a node in BST.

Now, let us implement one BST and see.

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#include <iostream>
#include <vector>

using namespace std;

class bst
{
private:
    struct node
    {
        int key;
        int data; // optional. Not used in this example.
        node* left_node;
        node* right_node;
        ~node()
        {
            cout << "node is deleted" << endl;
        }
    };
    
    node *root;
    
    node* create_new_node(node** this_node, int key)
    {
        if(*this_node == nullptr)
        {
            // found a free node 
            *this_node = new node;
            (*this_node)->left_node = nullptr;
            (*this_node)->right_node = nullptr;
            
            return *this_node;
        }
        
        // start traversing 
        if(key == (*this_node)->key)
        {
            return nullptr; // cannot create duplicate key 
        }
        else if(key < (*this_node)->key)
        {
            // traverse left 
            return create_new_node(&((*this_node)->left_node), key);
        }
        else if(key > (*this_node)->key)
        {
            // traverse right 
            return create_new_node(&((*this_node)->right_node), key);
        }
        
    }
    
    void print_keys(node* this_node)
    {
        if(this_node != nullptr)
        {
            print_keys(this_node->left_node);    // start traversing to the left to print 
                                                 // in ascending order
            cout << "key : " << this_node->key << endl; // print this node key 
            print_keys(this_node->right_node);  // now, traverse in the right to print
                                                // the higher keys in order 
        }
        
        return;
    }
    
    node* find_min_value_node(node *this_node)
    {
        node *temp_node = this_node;
        
        if( this_node->left_node != nullptr )
        {
            temp_node = find_min_value_node(this_node->left_node);
        }
        
        return temp_node;
    }
    
    void delete_this_key(node **this_node, int key, int& status)
    {
        // step 1 : find the node
        if(*this_node == nullptr)
        {
            // reached a null-node 
            cout << "Key not found. Cannot be deleted" << endl;
            status = -1;
            return;
        }
        
        if(key == (*this_node)->key)
        {
            // found our node 
            status = 0;
            // now, check the children
            // case 1 - There are no children. Then simply delete this node 
            if(    ( (*this_node)->left_node == nullptr ) 
                && ( (*this_node)->right_node == nullptr ) )
            {
                delete *this_node;
                *this_node = nullptr;
            }
            // case 2 - this_node has one right child 
            else if( (*this_node)->left_node == nullptr )
            {
                // copy the right node byte by byte to this node 
                // and delete the right node 
                // i.e. move the right node to this node 
                node *temp = (*this_node)->right_node;
                (*this_node)->key = (*this_node)->right_node->key;
                (*this_node)->left_node = (*this_node)->right_node->left_node;
                (*this_node)->right_node = (*this_node)->right_node->right_node;                
                // Now, delete the original right node, as it is moved to this node
                delete temp;           
            }
            // case 3 - this_node has one left child 
            else if( (*this_node)->right_node == nullptr )
            {
                // copy the left node byte by byte to this node 
                // and delete the left_node node 
                // i.e. move the left_node node to this node 
                node *temp = (*this_node)->left_node;
                (*this_node)->key = (*this_node)->left_node->key;
                (*this_node)->left_node = (*this_node)->left_node->left_node;
                (*this_node)->right_node = (*this_node)->left_node->right_node;                
                // Now, delete the original left node, as it is moved to this node
                delete temp;                     
            }
            // case 4 - this node has two children both left and right 
            else 
            {
                node *temp = find_min_value_node( (*this_node)->right_node );
                // found a node with minimum value and with no left nodes 
                // copy the key of minimum node to this node and delete that temp node
                (*this_node)->key = temp->key;
                // clean the right node link properly
                // check whether the temp node is actually this_node's right node
                // in that case this_node's right_node should be set to nullptr
                if((*this_node)->key == (*this_node)->right_node->key)
                {
                    // check if this temp node is a leaf node
                    if(   (temp->left_node == nullptr )
                        &&(temp->right_node == nullptr) )
                    {
                        (*this_node)->right_node = nullptr;
                    }
                }
                
                int temp_status = 0;
                delete_this_key(&temp, temp->key, temp_status);
                
            }
        }
        else if(key < (*this_node)->key)
        {
            // traverse left 
            delete_this_key(&((*this_node)->left_node), key, status);
        }
        else if(key > (*this_node)->key)
        {
            // traverse right 
            delete_this_key(&((*this_node)->right_node), key, status);
        }
        
        return;
    }
    
    void get_all_nodes(vector<node*>& nodes, node* this_node)
    {
        if(this_node != nullptr)
        {
            get_all_nodes(nodes, this_node->left_node);    // start traversing to the left to print 
                                                           // in ascending order
            nodes.push_back(this_node);
            get_all_nodes(nodes, this_node->right_node);  // now, traverse in the right to print
                                                          // the higher keys in order 
        }
        
        return;
    }
    
public:
    bst():root{nullptr}{}
    ~bst()
    {
        // get the nodes one by one in to a vector and delete them 
        vector<node*> nodes;
        get_all_nodes(nodes, root);
        
        for (auto this_node : nodes)
        {
            delete this_node;
        }
        
        cout << "\nbst is destroyed" << endl;
    }
    
    int insert(int key)
    {
        int status = -1;
        node* new_node = create_new_node(&root, key);
        
        if(new_node != nullptr)
        {
            // a new node is created 
            new_node->key = key;
            status = 0;
        }
        
        return status;
    }
    
    void print()
    {
        cout << "\nprinting keys..." << endl;
        print_keys(root);
        cout << "printing complete\n" << endl;
    }
    
    int delete_key(int key)
    {    
        int status = 0;
        delete_this_key(&root, key, status);
        return status;
    }
    
};


int main()
{
    bst bst_1;
    // '1', '5' and '2' are duplicates 
    int unsorted_array[] = {5, 2, 7, 9, 10, 1, 6, 12, 3, 1, 5, 2}; 
    int status = 0;
    for (auto val : unsorted_array)
    {
        status = bst_1.insert(val);
        if(status == 0)
        {
            cout << "val : " << val << " inserted" << endl;
        }
        else
        {
            cout << "val : " << val << " not inserted" << endl;
        }
    }
    
    bst_1.print();
    
    // delete 1
    status = bst_1.delete_key(1);
    if(status == 0)
    {
        cout << "Val : 1 deleted" << endl; 
    }
    else
    {
        cout << "Val : 1 not deleted" << endl;
    }
    
    bst_1.print();
    
    // delete 9
    status = bst_1.delete_key(9);
    if(status == 0)
    {
        cout << "Val : 9 deleted" << endl; 
    }
    else
    {
        cout << "Val : 9 not deleted" << endl;
    }
    
    bst_1.print();
    
    // delete 7
    status = bst_1.delete_key(7);
    if(status == 0)
    {
        cout << "Val : 7 deleted" << endl; 
    }
    else
    {
        cout << "Val : 7 not deleted" << endl;
    }
    
    bst_1.print();
    
    // delete 10
    status = bst_1.delete_key(10);
    if(status == 0)
    {
        cout << "Val : 10 deleted" << endl; 
    }
    else
    {
        cout << "Val : 10 not deleted" << endl;
    }
    
    bst_1.print();
    
    // try to delete 1 again 
    status = bst_1.delete_key(1);
    if(status == 0)
    {
        cout << "Val : 1 deleted" << endl; 
    }
    else
    {
        cout << "Val : 1 not deleted" << endl;
    }
    
    bst_1.print();
    
    // try to delete 7 again 
    status = bst_1.delete_key(7);
    if(status == 0)
    {
        cout << "Val : 7 deleted" << endl; 
    }
    else
    {
        cout << "Val : 7 not deleted" << endl;
    }
    
    bst_1.print();
    
    cout << "Main ends" << endl;
    
    return 0;
}

Let us visualize how the unsorted array data are getting stored in our BST bst_1 as sorted keys:

Some points to consider for understanding how I have tested our BST:

  • To show that duplicate keys cannot be created I have used values 1, 5 and 2.
  • For deletion purposes I have used the following keys:
    • key -1 that has no child,
    • key-9 that has one right child
    • key 7 that has two children
    • key 10 that has one right child
  • Also, tried to delete key-1 and key-7 again.

Let us see the result now.

val : 5 inserted
val : 2 inserted
val : 7 inserted
val : 9 inserted
val : 10 inserted
val : 1 inserted
val : 6 inserted
val : 12 inserted
val : 3 inserted
val : 1 not inserted
val : 5 not inserted
val : 2 not inserted

printing keys...
key : 1
key : 2
key : 3
key : 5
key : 6
key : 7
key : 9
key : 10
key : 12
printing complete

node is deleted
Val : 1 deleted

printing keys...
key : 2
key : 3
key : 5
key : 6
key : 7
key : 9
key : 10
key : 12
printing complete

node is deleted
Val : 9 deleted

printing keys...
key : 2
key : 3
key : 5
key : 6
key : 7
key : 10
key : 12
printing complete

node is deleted
Val : 7 deleted

printing keys...
key : 2
key : 3
key : 5
key : 6
key : 10
key : 12
printing complete

node is deleted
Val : 10 deleted

printing keys...
key : 2
key : 3
key : 5
key : 6
key : 12
printing complete

Key not found. Cannot be deleted
Val : 1 not deleted

printing keys...
key : 2
key : 3
key : 5
key : 6
key : 12
printing complete

Key not found. Cannot be deleted
Val : 7 not deleted

printing keys...
key : 2
key : 3
key : 5
key : 6
key : 12
printing complete

Main ends
node is deleted
node is deleted
node is deleted
node is deleted
node is deleted

bst is destroyed

Deleting nodes in Binary Search Tree

BST is always tricky when it comes to deleting a node as you cannot simply go and find the node and delete it, as it breaks the links in between and the BST will become useless.

We must consider three cases during a node deletion:

  • Node has no child. In that case simply delete that node
  • Node has one child. Either a left child or a right child. In this case you can move that one child to the ‘node’ including the child’s key, left and right child node pointers if any, and then delete that child node. In effect, the node to be deleted has become its child node and the original child node got deleted. Remember the move assignment operation we have done in chapter 6? This has a resemblance with that. Hasn’t it?
  •  Node has two children. In this case, we must first find the minimum value node that has no children of its own, or at least, there should not be a left child. We must copy that minimum value node’s key to this node.
    • There is a special case here, i.e. if that minimum value node and our current node ‘s right child node are the same. In this special case we need to check one extra condition. i.e. whether this minimum value node is a leaf node. A leaf node in a BST is a node with no left and right children. In this case, our current node’s right child should be made as nullptr, otherwise this link will be broken and will lead to errors in course of execution.

Enjoyed the chapter? Let me know in the comments below. Thanks!😊

0

Chapter 20 : Series On Design Patterns – Bridge Pattern

Key Words: Bridge design pattern

Topics at a glance:

  • Let us bridge the abstract and the implementation entities
  • Let us turn on an incandescent bulb and adjust its brightness
  • Let us make a florescent bulb as well.

Bridge design pattern

Bridge is a structural design pattern, that separates the implementation and the abstraction into an orthogonally related two-class systems. One class system defines/represents the abstract entities, while another class system defines/represents the actual implementation entities.  

For explaining bridge pattern, I am taking the example of the Bulb and its State that we used in the Bulb Controller application of chapter 19. Could you identify the abstract class system in that application? The abstract class system that I am mentioning about is the State class and its derived classes On_State and Off_State. Why are they abstract? State itself is an abstract concept as I have explained in chapter 19. State object only knows to change from one sate to another and manipulate the behavior in each state. They do not really know how to implement the actual functionality that the machine has to perform in that state. For example, how a State class’s object instance will know how to turn on an electric bulb or how to turn off? It does not really.

We will come back to this example later in tis chapter. For citing another example for abstract class systems, consider Process and Thread in any platform.The actual implementation of “processes” and”threads” is platform dependent. Say, one can use C++ threads or posix threads in a program. But if the application/client code to an abstract base class with interfaces to create, run, join, and interact with the thread, what matters whether it’s a posix thread or C++ standard library thread implementation? Coming to process now. A process is defined/implemented by the underlying platform such as Widows, Linux, IOS operating systems or some virtual machine hypervisor. The client only programs to an abstract base class Process. It needs interfaces to create, fork, manage and to kill a process. That’s all the client bothers about.

In normal course, if we are writing a code to only one specific implementation, then no need to have separate abstract class system and implementation class system in your design. But if you want to write a (semi-) portable application that should run regardless of the underlying platform/implementation, then it is imperative that you must program to abstract class systems. Now, the focus comes to this; How you are going to define this abstract class system? Usually through inheritance. We define an abstract base class, and derive concrete classes that are specific to the platform – say Windows, Linux, Posix or C++ standard library. But what if we introduce another platform in future, say, a virtual hypervisor or a simple bare-metal system with a minimal layer supporting process/threads. We cannot continue to inherit which will lead to inheritance misuse as we saw in case of the Page class that I used as an example in chapter 16 – for understanding the need for decorator design pattern. But here, we cannot resort to decorator pattern, as we need a way to abstract the implementation and our purpose is not to add features/functionalities on top of a base class. We will need a way to separate the abstract class system from the implementation class system. This is where bridge pattern comes in for help.

Bridge pattern separates the abstract class system that define abstract entities from actual implementation class system that is platform dependent. Let us come back to our light control application. Here, user need a feature to add support for incandescent bulbs that supports dimming/adjusting the brightness and for fluorescent bulb technology that gives more light for lesser power.

Turning on, off, changing the brightness cannot be defined in the abstract Bulb class or in the abstract State class. We need another implementation specific class system called the Light_Device class and its concrete derived classes that implements the functionality to support incandescent and fluorescent technologies.

Now, Bulb class will also contain a pointer reference to the Light_Device object instance in addition to the State instance. But, Bulb, as before, delegates the state manipulation to its State object and State object after managing the state, delegates the task for turning on or turning off the light bulb to the Light_Device object instance that the Bulb object possess. Bulb class also have to define one interface to get the Light_Device object instance for delegating these tasks to turn on, off and change brightness to Light_Device object through State object.

Now, let us see the bridge pattern in action.

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
#include <iostream>
#include <string>


using namespace std;

enum class light_technology{incandescent = 0, fluorescent};

class Light_Device
{
public:
    ~Light_Device(){}
    virtual void on() =0;
    virtual void off() =0;
    virtual void change_brightness() =0;
};

class Incandescent_Device : public Light_Device
{
private:
    float current_brightness;
public:
    Incandescent_Device() : current_brightness{0.0F}
    {}
    ~Incandescent_Device(){}
    void on()
    {
        current_brightness = 100.0F;
        cout << "Incandescent bulb turned on" << endl;
    }
    
    void off()
    {
        current_brightness = 0.0F;
        cout << "Incandescent bulb turned off" << endl;
    }
    
    void change_brightness()
    {
        cout << "Enter the required brightness level" << endl;
        float percent;
        cin >> percent;
        if((percent < 0.0F) || (percent > 100.0F))
        {
            cout << "Invalid brightness entered" << endl;
        }
        else 
        {
            current_brightness = percent;
            cout << "Brightness set at " << current_brightness << endl; 
        }
        
    }
    
};

class Fluorescent_Device : public Light_Device
{
public:
    Fluorescent_Device(){}
    ~Fluorescent_Device(){}
    void on()
    {
        cout << "Fluorescent bulb turned on" << endl;
    }
    
    void off()
    {
        cout << "Fluorescent bulb turned off" << endl;
    }
    
    void change_brightness()
    {
        cout << "This operation not supported for fluorescent device" << endl;        
    }
    
};

class Bulb;

class State
{
public:
    State(){}
    virtual ~State(){}
    virtual void on(Bulb *bulb);
    virtual void off(Bulb *bulb);
    virtual void change_brightness(Bulb *bulb);
};

class Bulb
{
private:
    State *current;
    Light_Device *light_device;
public:
    Bulb(light_technology technology, State *state) : current{state}
    {
        switch(technology)
        {
            case light_technology::fluorescent:
            {
                light_device = new Fluorescent_Device();
                break;
            }
            case light_technology::incandescent:
            default:
            {
                light_device = new Incandescent_Device();
                break;
            }            
        }
    }
    ~Bulb()
    {
        delete light_device;
    }
    
    void set_current_state(State *state)
    {
        current = state;
        return;
    }
    
    void on()
    {
        current->on(this);
        return;
    }
    
    void off()
    {
        current->off(this);
        return;
    }
    
    void change_brightness()
    {    
        current->change_brightness(this);
        return;
    }
    
    Light_Device* get_light_device()
    {
        return light_device;
    }
    
};


void State::on(Bulb *bulb)
{
    cout << "already in on state" << endl;
    return;
}

void State::off(Bulb *bulb)
{
    cout << "already in off state" << endl;
    return;
}

void State::change_brightness(Bulb *bulb)
{
    cout << "Not supported in off state" << endl;
    return;
}


class On_State : public State
{
public:
    On_State(){}
    ~On_State(){}
    
    void on(Bulb *bulb);
    void off(Bulb *bulb);
    void change_brightness(Bulb *bulb);
};

class Off_State : public State
{
public:
    Off_State(){}
    ~Off_State(){}
    
    void on(Bulb *bulb);
    void off(Bulb *bulb);
    void change_brightness(Bulb *bulb);
};

void On_State::on(Bulb *bulb)
{
    // nothing to implement
    // just delegate this to base class 
    State::on(bulb);
}

void On_State::change_brightness(Bulb *bulb)
{
    auto light_device = bulb->get_light_device();
    light_device->change_brightness();
}

void On_State::off(Bulb *bulb)
{
    // create an Off_State instance
    Off_State *new_state = new Off_State();
    bulb->set_current_state(new_state);
    auto light_device = bulb->get_light_device();
    light_device->off();
    
    delete this;
}

void Off_State::on(Bulb *bulb)
{    
    // create an Off_State instance
    On_State *new_state = new On_State();
    bulb->set_current_state(new_state);
    auto light_device = bulb->get_light_device();
    light_device->on();
    
    delete this;
}

void Off_State::off(Bulb *bulb)
{
    // nothing to implement
    // just delegate this to base class 
    State::off(bulb);
    
    return;
}

void Off_State::change_brightness(Bulb *bulb)
{
    // nothing to do 
    // just delegate to base class 
    State::change_brightness(bulb);
}


int main()
{
    string choice;
    string technology;
    Bulb *b1;
    
    cout << "Bulb controller application" << endl;
    cout << "Enter the bulb technology required :\n(I) : Incandescent\n(F) : Fluorescent" << endl;
    
    cin >> technology;
    
    if(0 == technology.compare("F"))
    {
        b1 = new Bulb(light_technology::fluorescent, new Off_State());
    }
    else
    {
        b1 = new Bulb(light_technology::incandescent, new Off_State());
    }
    
    
    while(true)
    {
        choice.clear();
        cout << "What to do next : ON, OFF, SET_BRIGHTNESS, EXIT" << endl;
        cin >> choice;
        
        if(0 == choice.compare("ON"))
        {
            b1->on();
        }
        else if(0 == choice.compare("OFF"))
        {
            b1->off();
        }
        else if(0 == choice.compare("SET_BRIGHTNESS"))
        {
            b1->change_brightness();
        }
        else if(0 == choice.compare("EXIT"))
        {
            cout << "Exiting bulb controller application" << endl;
            break;
        }
    }
    
    if(b1 != nullptr)
    {
        delete b1;
    }
    
    return 0;
}

Want to see the result?

Bulb controller application
Enter the bulb technology required :
(I) : Incandescent
(F) : Fluorescent
I
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
ON
Incandescent bulb turned on
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
SET_BRIGHTNESS
Enter the required brightness level
50
Brightness set at 50
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
OFF
Incandescent bulb turned off
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
EXIT
Exiting bulb controller application

Incandescent bulb support changing the brightness. Now let us see what happens when the user chooses to create a fluorescent bulb instead.

Bulb controller application
Enter the bulb technology required :
(I) : Incandescent
(F) : Fluorescent
F
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
ON
Fluorescent bulb turned on
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
SET_BRIGHTNESS
This operation not supported for fluorescent device
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
OFF
Fluorescent bulb turned off
What to do next : ON, OFF, SET_BRIGHTNESS, EXIT
EXIT
Exiting bulb controller application

See the difference now? Both the Bulb and State classes does not really bother about the implementation. They just delegate the task that depends upon the implementation to the relevant implementation object. i.e. Light_Device instance. That’s the advantage of bridge pattern.

Enjoyed the chapter? Let me know in the comments below. Thanks 😊

0

Chapter 19 : Series On Design Patterns – State Pattern

Key Words: State design pattern

Topics at a glance:

  • Delegating state manipulation using state objects.
  • Let us turn on a bulb!

State design pattern

In this chapter we will see another behavioral pattern – the state pattern. We have all heard about Finite State Machines (FSM). FSM abstracts the behavior of the machine into identifiable unique entities called states. At any point of time, an FSM will be in a unique state and will exhibit behavior appropriate to that state. FSM can change from one state to another state upon the trigger of a specific event/condition.

In state pattern, we basically split the state and the machine. It is more correct to say that a machine is at a state rather than a machine has a state. In state pattern we make a State class as a base class and make each state the machine support into separate derived classes. Machine as said before, is always at a state in any given context. That said, a machine here, can be treated as a context class which will be at a state at any point of time. Machine will have a reference (pointer) to its unique state object that identifies/realizes its actual state, in that context. Machine will implement certain interfaces to manipulate its state for the clients. Machine will delegate the actual state manipulation to the state object. State object instance manages the state by executing the set of actions required to be performed in that state and also implements the necessary functionality to transition to another state as required.

For demonstrating an actual state pattern implementation, I am taking an example of a light bulb that can be turned on and off. Here the machine is defined by the Bulb class and it’s identifiable states are On_State class and Off_State class; State class being the abstract base class that machine will use to manipulate its state.

Let us see this in action.

  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
#include <iostream>
#include <string>


using namespace std;

class Bulb;

class State
{
public:
    State(){}
    virtual ~State(){}
    virtual void on(Bulb *bulb);
    virtual void off(Bulb *bulb);
};

class Bulb
{
private:
    State *current;
public:
    Bulb(State *state) : current{state} {}
    ~Bulb(){}
    
    void set_current_state(State *state)
    {
        current = state;
    }
    
    void on()
    {
        current->on(this);
    }
    
    void off()
    {
        current->off(this);
    }
    
};


void State::on(Bulb *bulb)
{
    cout << "already in on state" << endl;
    return;
}

void State::off(Bulb *bulb)
{
    cout << "already in off state" << endl;
    return;
}


class On_State : public State
{
public:
    On_State(){}
    ~On_State(){}
    
    void on(Bulb *bulb);
    void off(Bulb *bulb);
};

class Off_State : public State
{
public:
    Off_State(){}
    ~Off_State(){}
    
    void on(Bulb *bulb);
    void off(Bulb *bulb);
};

void On_State::on(Bulb *bulb)
{
    // nothing to implement
    // just delegate this to base class 
    State::on(bulb);
}

void On_State::off(Bulb *bulb)
{
    // create an Off_State instance
    Off_State *new_state = new Off_State();
    bulb->set_current_state(new_state);
    cout << "Bulb is switched off" << endl;
    
    delete this;
}

void Off_State::on(Bulb *bulb)
{    
    // create an Off_State instance
    On_State *new_state = new On_State();
    bulb->set_current_state(new_state);
    cout << "Bulb is switched on" << endl;
    
    delete this;
}

void Off_State::off(Bulb *bulb)
{
    // nothing to implement
    // just delegate this to base class 
    State::off(bulb);
    
    return;
}

int main()
{
    string choice;
    
    Bulb b1(new Off_State());
    
    cout << "Bulb controller application" << endl;
    
    while(true)
    {
        choice.clear();
        cout << "What to do next : ON, OFF, EXIT" << endl;
        cin >> choice;
        
        if(0 == choice.compare("ON"))
        {
            b1.on();
        }
        else if(0 == choice.compare("OFF"))
        {
            b1.off();
        }
        else if(0 == choice.compare("EXIT"))
        {
            cout << "Exiting bulb controller application" << endl;
            break;
        }
    }
    
    return 0;
}

Want to see the result?

Bulb controller application
What to do next : ON, OFF, EXIT
ON
Bulb is switched on
What to do next : ON, OFF, EXIT
OFF
Bulb is switched off
What to do next : ON, OFF, EXIT
OFF
already in off state
What to do next : ON, OFF, EXIT
ON
Bulb is switched on
What to do next : ON, OFF, EXIT
ON
already in on state
What to do next : ON, OFF, EXIT
OFF
Bulb is switched off
What to do next : ON, OFF, EXIT
EXIT
Exiting bulb controller application

Here, one thing to note is that, when the bulb is in ON state, a command to switch it on, will result in an error. Similarly, when bulb is in OFF state, you cannot turn it off again. i.e. On_State will only define the functionality to transition to OFF state, whereas Off_State defines the functionality to transition to ON state. Machine itself doesn’t need to track in which state it is currently at, in its course of execution. That said, using state pattern, we have separated the state and the machine.

Please go through the next chapter, which will improvise this Bulb controller application using bridge pattern.

Enjoyed the chapter? Let me know in the comments below. Thanks 😊

1+

Chapter 18 : Series On Design Patterns – Singleton Pattern

Key Words: Singleton pattern, logger

Topics at a glance:

  • Playing safe with singletons – how to incorporate singleton design pattern in multi-threaded environment
  • Let’s put singleton and concurrency into action into a logger application

Singleton pattern

We all know what a singleton pattern is. It is used for preventing creation of more than one instance of a class. For explaining singleton pattern, I have come up with a suitable example – A logger.

Logger

The logger class is a composite class; it has a queue, which is used for storing the received messages and getting messages one by one out of the queue and finally prints it.

There should be only one logger instance. The same instance will be shared between multiple threads and they all try to log messages through that shared instance. Logger class has one printer thread to pull the received messages one by one and prints it out to the console output. This printer thread’s print function is made as a friend function to this logger class for convenience reasons. i.e. to avoid the syntax horrors in passing a member function to a thread.

i.e. There are multiple producers here and one consumer.

Since logger instance is shared between multiple threads, a matter of shared ownership is there and should be managed through std::shared_ptr. Since we must make multiple producer threads, thread_guards should be in place to manage them.

And finally, one more puzzle to be solved. Since multiple threads will try to get the singleton object instance, there is chance for race condition. The only variable that we are checking in the get_logger_instance() static member function is a static boolean flag ‘instance_created’. So, to avoid a likely race condition, we have to protect it with a mutex, managed through a unique_lock. Note: A race condition here may result in failure of singleton design and may create multiple logger instances.

So that’s it. For the singleton logger class, we have to use almost all C++ features that we have discussed so far:

  • Static members
  • Static member functions
  • Private constructor
  • Mutex locks
  • Unique locks
  • Shared pointers
  • Friend function
  • “Has a” relationship
  • thread guards

All these will be used in our logger class. Let us see this in action now.

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#include <iostream>
#include <thread>
#include <condition_variable>
#include <string>
#include <memory>
#include <atomic>

using namespace std;

template<typename T>
class Queue
{
public:
    enum class error_codes{bad_size = -1};
private:
    T *memory;
    int size_;
    int head;
    int tail;
    bool is_full;
    bool is_empty;
    std::mutex mutex_;
    std::condition_variable cond_var;
    bool data_ready;
public:
    explicit Queue(const int size = 0) : 
        size_{size}, data_ready{false}, is_full{false}, is_empty{true}
    {
        if(size_ <= 0)
        {
            cout << "Bad size specified" << endl;
            throw error_codes::bad_size;
        }
        
        memory = new T[size_];
        
        head = 0;
        tail = 0;
        cout << "Queue with size : " << size_ << " is created " << endl;
    }
    
    ~Queue()
    {
        delete []memory;
        head = 0;
        tail = 0;
        cout << "Queue with size : " << size_ << " is destroyed" << endl;
        size_ = 0;
    }
    
    // delete copy/move operations 
    Queue(const Queue& other_queue) =delete;
    Queue& operator=(const Queue& other_queue) =delete;
    Queue(Queue&& other_queue) =delete;
    Queue& operator=(Queue&& other_queue) =delete;
    
    
    bool insert(const T data)
    {
        bool ret = true;
        std::unique_lock<mutex> lck_(mutex_);
        
        if( is_full == false )
        {
            memory[tail] = data;
            tail = (tail + 1) % size_;
            if(tail == head)
            {
                is_full = true;
            }
            is_empty = false;
        }
        else
        {
            // queue is full
            cout << "\tQueue is full : " << data  << endl;
            ret = false;
        }
        
        if(ret == true)
        {
            data_ready = true;
            cond_var.notify_one(); // unlocks the lock within the function 
        }
        
        //lck_.unlock(); // actually unnecessary to do this for unique_lock 
        
        return ret;
    }
    
    bool get(T * data)
    {
        bool ret = true;
        
        unique_lock<mutex> lck(mutex_);
        
        // predicate 'check for data_ready' is VERY IMPORTANT here 
        cond_var.wait(lck, [this]{return this->data_ready;} );
        
        if(is_empty == false)
        {
            *data = memory[head];
            head = (head + 1) % size_;
            is_full = false;
            if(head == tail)
            {
                is_empty = true;
            }
        }
        else
        {
            // queue empty
            cout << "\tQueue is empty" << endl;
            ret = false;
        }
        
        return ret;
    }
    
    int size() const
    {
        return size_;
    }
    
};

class logger;
void print(const logger* this_logger);

class logger
{
private:
    static shared_ptr<logger> instance;
    static std::mutex mutex_;
    static bool instance_created;
    
    int size_;
    Queue<std::string> *queue;
    std::thread printer_thread;
    
    // private constructor
    logger() : 
        size_{100},  
        queue( new Queue<std::string>(size_) ),
        printer_thread(print, this)
    {
        cout << "Logger instance created" << endl;
    }
    
public:
    ~logger()
    {
        end_session();
        printer_thread.join();
        delete queue;
        cout << "Logger destroyed" << endl;
    }
    
    static shared_ptr<logger> get_logger_instance()
    {
        unique_lock<std::mutex> lck(mutex_);
        
        if(instance_created == false)
        {
            // creates a logger instance
            instance.reset(new logger());
            instance_created = true;
            lck.unlock();
        }
        else
        {
            cout << "Logger instance already created. Returning instance" << endl;
        }
        
        return instance;
    }
    
    bool log_info(const string& info)
    {
        string temp("INFO : ");
        temp += info;
        return queue->insert(temp);
    }
    
    bool log_error(const string& error)
    {
        string temp("ERROR : ");
        temp += error;
        return queue->insert(temp);
    }
    
    bool end_session()
    {
        string temp("END_LOG");
        return queue->insert(temp);
    }
    
    friend void print(const logger *this_logger);
    
};

std::mutex logger::mutex_;
bool logger::instance_created = false;
shared_ptr<logger> logger::instance(nullptr);

void print(const logger *this_logger)
{
    string message;
    cout << "printer thread spawned" << endl;
    while(true)
    {
        this_logger->queue->get(&message);
        cout << message << endl;
        
        if(0 == message.compare("END_LOG"))
        {
            cout << "Printer thread execution complete" << endl;
            break; 
        }
    }
    
    return;
}

void log_hello(void)
{
    shared_ptr<logger> sh_logger = logger::get_logger_instance();
    logger *logger_instance = sh_logger.get();
    
    for(int count = 0; count < 5; ++count)
    {
        logger_instance->log_info(" Hello ");
    }
    
    return;
}

void log_world(void)
{
    shared_ptr<logger> sh_logger = logger::get_logger_instance();
    logger *logger_instance = sh_logger.get();
    
    for(int count = 0; count < 5; ++count)
    {
        logger_instance->log_info(" World ");
    }
    
    return;
}

class thread_guard
{
private:
    std::thread this_thread;
public:
    explicit thread_guard(void(*th_function)(void)):
        this_thread(th_function)
    {
        // do nothing
    }
    ~thread_guard()
    {
        this_thread.join();
        cout << "Thread_Guard : Thread joined" << endl;
    }
    // delete copy/move operations
    thread_guard(const thread_guard&) =delete;
    thread_guard(thread_guard&&) =delete;
    thread_guard& operator=(const thread_guard&) =delete;
    thread_guard& operator=(thread_guard&&) =delete;
};

int main()
{    
    thread_guard guard_hello_thread(log_hello);
    thread_guard guard_world_thread(log_world);
    
    cout << "Main ends" << endl;
    
    return 0;
}

Want to see the result?

Queue with size : 100 is created 
Main ends
Logger instance created 
printer thread spawned
INFO :  Hello
Logger instance already created. Returning instance
INFO :  Hello
Thread_Guard : Thread joined
INFO :  Hello
Thread_Guard : Thread joined
INFO :  Hello
INFO :  Hello
INFO :  World
INFO :  World
INFO :  World
INFO :  World
INFO :  World
END_LOG
Printer thread execution complete
Queue with size : 100 is destroyed
Logger destroyed

Enjoyed the chapter? Let me know in the comments below. Thanks 🙂

0

Chapter 17 : Series On Design Patterns – Strategy Pattern

Key Words: Strategy design pattern

Topics at a glance:

  • Let us strategize before action
  • Document Editor App is now version 4.0!

Strategy design pattern

In this chapter we will see another behavioral pattern called the strategy pattern. Strategy helps you to encapsulate algorithms inside object instances of a base-class-derived-class system. It exposes only a minimal interface to configure/rather select the algorithm required. This portion is called selecting the strategy. Once the user selects the strategy, the algorithm applicable to that context will run and gives the result. User will code to a base class instance without even knowing which derived class instance is implementing the algorithm. Varying implementations of a family of algorithms will be encapsulated within each of the derived classes. Base class delegates specific implementation of algorithms to it’s derived classes.

For explaining strategy, I am taking our Document Application as an example. Till version 3.0, our Document application is just an application framework with basic elements for user interaction such as menus and menu items. It does not actually read or write any files. So, let us make this document editor a real document editor which can open real files for reading or writing.

Let us see the role of strategy design pattern in this context. When User selects an option to edit the document, he will be prompted with the basic formatting options such as ‘width’ of the document and ‘alignment‘ type. User can select align to ‘left’ or ‘right’. Now, the Document class can use these user inputs to select the formatting strategy and delegates this to an object instance of a specific formatter sub-class. Strategy pattern is used to design, the formatter class. Formatter is our base class. Formatter can read the lines from user/console. For aligning purposes, it has to seek help of its derived class instances. i.e. Formatter class only declares a pure virtual align() member function. Derived classes ‘align_left_formatter’ and ‘align_right_formatter’ implements the actual algorithm (strategy) for left and right alignment, respectively.

That is the philosophy of strategy pattern. i.e. to encapsulate a family of (related) algorithm implementations in derived classes.

The client will program to an abstract base class and select the algorithm or the way the client is expecting the result. Here, client program is the Document class itself which handles the files.

The client does the following:

1. It opens the file through a fstream object based on the user inputs.

2. It selects a strategy based on user inputs.

3. It creates an object instance of a specific formatter sub-class based upon the selected strategy.

4. It then uses this object instance to make the necessary formatting on the open file.

That said, formatter does not take care of opening and closing files. It just expects the client to give a valid file handle for a file stream opened in the appropriate mode (read, write, append, truncate etc.).

NOTE : Use manipulators defined in ‘iomanip‘ header files for opening the file stream in the required mode.

Let us now see our Strategy Pattern i.e. formatter class in action:

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <fstream>
#include <iomanip>

using namespace std;

class formatter
{
public:
    virtual ~formatter(){}
    
    void format(fstream &output_file)
    {
        string line;
        
        cout << "Start entering your text" << endl;
        cout << "Enter 'END' for ending the session" << endl;
        
        while(output_file)
        {
            getline(cin, line);
            
            if(line.compare("END") == 0)
            {
                break;
            }
            
            align(line);
            
            output_file << line << endl;
            line[0] = '\0';
        }
    }
private:
    virtual void align(string &) = 0;
};

class align_right_formatter : public formatter
{
private:
    int width_;
public:
    align_right_formatter(int width = 20) : width_{width}
    {
        
    }
private:
    void align(string & line) override
    {
        int len = line.length();
        const string space = " ";
        const string new_line = "\n";
        
        vector <string> line_lets;
        
        int rem_len = len;
        int pos = 0;
        int count = 1;
        int offset = 0;
        int org = 0;
        
        if(rem_len <= width_)
        {
            line_lets.push_back(line);
        }
        
        while(rem_len > width_)
        {
            offset = width_ * count;
            while(true)
            {
                if(line[offset] == ' ')
                {
                    if(offset - org <= width_)
                    {
                        break;
                    }
                    else if (offset - 1 < 0) 
                    {
                        break;
                    }
                    
                    // The string is still lengthy
                    // can't break here 
                    
                }
                --offset;
            }
            
            line.insert(offset, new_line);
            len = line.length();
            rem_len = (len - offset) + 1;
            org = len - rem_len;
            count++;
        }
        
        stringstream ss(line);
        string tmp;
        
        while(getline(ss, tmp, '\n'))
        {
            line_lets.push_back(tmp);
        }
        
        line.clear();
        
        for(auto str : line_lets)
        {
            int limit = width_ - str.length() - 1;
            
            for(int i = 0; i < limit; ++i)
            {
                str.insert(i, space);
            }
            
            line.append(str);
            line.append(new_line);
        }
        
        return;
    }

};

class align_left_formatter : public formatter
{
private:
    int width_;
public:
    align_left_formatter(int width = 20) : width_{width}
    {
        // do nothing
    }
private:
    void align(string & line) override
    {
        int count = 1;
        int offset = 0;
        int len = line.length();
        const string new_line{"\n"};
        int rem_len = len;
        int org = 0;
        
        while(rem_len >= width_)
        {
            offset = width_ * count;
            
            while(true)
            {
                if(line[offset] == ' ')
                {
                    if(offset - org <= width_)
                    {
                        break;
                    }
                    else if (offset - 1 < 0) 
                    {
                        break;
                    }
                    
                    // The string is still lengthy
                    // can't break here 
                    
                }
                --offset;
            }
            
            line.insert(offset + 1, new_line);
            len = line.length();
            rem_len = (len - offset) + 1;
            org = len - rem_len;
            count++;
        }
        
        return;
    }

};

class Command
{
public:
    virtual ~Command(){}
    virtual void execute() = 0;
};

class Selectable
{
public:
    virtual ~Selectable(){}
    virtual string get_name() = 0;
    virtual void select() = 0;    
};

class Menu_Item : public Selectable
{
private:
    string name_;
    unique_ptr<Command> command_object_;
public:
    Menu_Item(const char* const name) : 
        name_{name},
        command_object_{nullptr}
    {
    }
    
    ~Menu_Item()
    {
        cout << "Menu_Item : " << name_ << " destroyed" << endl;
        command_object_.reset();        
    }
    
    void add_command_object(Command *command_object)
    {
        command_object_.reset(command_object);
    }
    
    void select() override
    {
        click();
    }
    
    int click()
    {
        int status = -1;
        if(command_object_ != nullptr)
        {
            command_object_->execute();
            status = 0;
        }
        
        return status;
    }
    
    string get_name()
    {
        return name_;
    }
};

class Menu : public Selectable
{
private:
    string name_;
    vector<unique_ptr<Selectable>> menu_items;
public:
    Menu(const char* const name): 
        name_{name}
    {
        
    }
    
    ~Menu()
    {
        cout << "Menu " << name_ << " destroyed" << endl;
        for (auto &item : menu_items)
        {
            item.reset();
        }
    }
    
    void add_menu_item(unique_ptr<Selectable> menu_item)
    {
        menu_items.push_back(std::move(menu_item));
    }
    
    void select() override 
    {
        hover();
    }
    
    void hover()
    {
        int choice;
        int count = 0;
        
        for (auto &item : menu_items)
        {
            cout << item->get_name() << " : " << count++ << endl;
        }
        
        cout << "Enter your choice : ";
        cin >> choice;
        
        if(   (choice >= 0) 
            &&(choice <= menu_items.size()) )
        {
            menu_items[choice]->select();
            return;
        }
        
    }
    
    string get_name()
    {
        return name_;
    }
    
};

class Subject;

enum class doc_states{created = 0, opened, closed, changed, moved};

class Observer
{
public:
    ~Observer(){}
    virtual void update(doc_states current_state) = 0;
};

class Subject
{
public:
    ~Subject(){}
    virtual void notify() = 0;
    virtual void attach(Observer *this_observer) = 0;
    virtual void dettach(Observer *this_observer) = 0;
};

class Document : public Subject 
{
private:
    string name_;
    doc_states current_state;
    vector<Observer*> observers; 
    fstream file;
public:
    typedef void (Document::*doc_function)();
    Document(): current_state{doc_states::created}
    { 
        /* do nothing */ 
    }
    
    ~Document()
    {
        if(   (current_state != doc_states::closed)
            &&(current_state != doc_states::created) )
        {
            // i.e. a document is open. So close it.
            close();
        }
    }
    
    void open_read()
    {
        cout << "Enter the file name to open for reading" << endl;
        cin >> name_;
        
        
        file.open(name_, ios::in); // read only mode 
        
        if(file)
        {
            current_state = doc_states::opened;
            cout << "Document : " << name_ << " is open for reading!\n" << endl;
            string line;
            
            while(file >> line)
            {
                cout << line << endl;            
            }
            
            cout << "\nEnd of File!" << endl;
            
        }
    }
    
    void open_write()
    {
        cout << "Enter the file name to open for writing" << endl;
        cin >> name_;
        
        file.open(name_, ios::app); // write in append mode  
        formatter *frmtr = nullptr;
        
        if(file)
        {
            file << "\n" << endl;
            current_state = doc_states::opened;
            cout << "Document : " << name_ << " is open for writing" << endl;
            // start writing
            cout << "Select alignment : Left(0), Right(1)" << endl;
            
            int alignment_selected = 0;
            int width = 0;
            
            cin >> alignment_selected;
            
            cout << "Select width : " << endl;
            cin >> width;
            if( (width < 0) || (width > 30) )
            {
                width = 20;
            }
                
            if(alignment_selected == 1)
            {
                // right 
                frmtr = new align_right_formatter(width);
            }
            else
            {
                // left 
                frmtr = new align_left_formatter(width);
            }
            
            if(frmtr != nullptr)
            {
                frmtr->format(file);
                delete frmtr;
            }
            
        }
    }
    
    void close()
    {
       if(   (current_state != doc_states::closed)
            &&(current_state != doc_states::created) )
        {
            file.close();
            current_state = doc_states::closed;
            cout << "Document : " << name_ << " is closed" << endl;
            name_.clear();
            // notify the subscribed observers 
            notify();
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void copy(int start, int end, string &copied_text)
    void copy()
    {
        if(current_state != doc_states::closed)
        {
            cout << "copying..." << endl;
            // copy from start to end to copied_text
            cout << "copy complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void paste(int position, string& insert_text)
    void paste()
    {
        if(current_state != doc_states::closed)
        {
            cout << "pasting..." << endl;
            cout << "pasting complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void cut(int start, int end, string &extracted_text)
    void cut()
    {
        if(current_state != doc_states::closed)
        {
            cout << "cutting..." << endl;
            // copy from start to end to copied_text
            cout << "cut complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    // support for Observer pattern
    void notify() override
    {
        for( auto &observer : observers )
        {
            observer->update(current_state);
        }
    }
    
    void attach(Observer *this_observer) override
    {
        observers.push_back(this_observer);
    }
    
    void dettach(Observer *this_observer) override
    {
        int position = 0;
        auto begin = observers.begin();
        auto end = observers.end();
        
        for( auto itr = begin; itr < end; ++itr )
        {
            auto obs = *itr;
            if(this_observer == obs)
            {
                observers.erase(itr);
            }
        }
    }
    
};

class Open_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Open_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Close_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Close_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Copy_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Copy_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Paste_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Paste_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Cut_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Cut_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Application : public Observer
{
private:
    vector<unique_ptr<Menu>> menus;
    shared_ptr<Document> current_doc;
    enum class signal{app_launched = 0, app_exit, user_interrupt, other, no_trigger};
    signal trigger;
    
    void prompt_to_open_new_document()
    {
        // prompt the user 
        cout << "Open a document for editing" << endl;        
        return;
    }
    
public:
    Application()
    {
        string doc_name;
        trigger = signal::app_launched;
        
        cout << "Document Editor App, Version : 4.0" << endl;
        
        shared_ptr<Document> document = make_shared<Document>();
        current_doc = document;

        // Attach this 'Application' as an Observer of document 
        (document.get())->attach(this);
        
        // add Menus "File" and "Edit"
        unique_ptr<Menu> file_menu = make_unique<Menu>("File");
        unique_ptr<Menu> open_sub_menu = make_unique<Menu>("Open");
        unique_ptr<Menu_Item> open_menu_item_1 = make_unique<Menu_Item>("Open For Read");
        unique_ptr<Menu_Item> open_menu_item_2 = make_unique<Menu_Item>("Open For Write");
        
        
        Open_Document_Command *open_read_cmd_object = new Open_Document_Command(document, &Document::open_read);
        Open_Document_Command *open_write_cmd_object = new Open_Document_Command(document, &Document::open_write);
        open_menu_item_1->add_command_object(open_read_cmd_object);
        open_menu_item_2->add_command_object(open_write_cmd_object);
        
        open_sub_menu->add_menu_item(std::move(open_menu_item_1));
        open_sub_menu->add_menu_item(std::move(open_menu_item_2));
        
        file_menu->add_menu_item(std::move(open_sub_menu));
        
        unique_ptr<Menu_Item> close_item = make_unique<Menu_Item> ("Close");
        Close_Document_Command *close_cmd_object = new Close_Document_Command(document, &Document::close);
        close_item->add_command_object(close_cmd_object);
        
        file_menu->add_menu_item(std::move(close_item));
        
        unique_ptr<Menu> edit_menu = make_unique<Menu>("Edit");
        unique_ptr<Menu_Item> copy_item = make_unique<Menu_Item>("Copy");
        Copy_Document_Command *copy_cmd_object = new Copy_Document_Command(document, &Document::copy);
        copy_item->add_command_object(copy_cmd_object);
        
        edit_menu->add_menu_item(std::move(copy_item));
        
        unique_ptr<Menu_Item> paste_item = make_unique<Menu_Item>("Paste");
        Paste_Document_Command *paste_cmd_object = new Paste_Document_Command(document, &Document::paste);
        paste_item->add_command_object(paste_cmd_object);
        
        edit_menu->add_menu_item(std::move(paste_item));
        
        unique_ptr<Menu_Item> cut_item = make_unique<Menu_Item>("Cut");
        Cut_Document_Command *cut_cmd_object = new Cut_Document_Command(document, &Document::cut);
        cut_item->add_command_object(cut_cmd_object);
        
        edit_menu->add_menu_item(std::move(cut_item));
        
        menus.push_back(std::move(file_menu));
        menus.push_back(std::move(edit_menu));
    }
    
    int select_menu()
    {
        int choice = 0;
        int end_app = 0;
        cout << "Enter your choice : " << endl;
        for(auto &menu : menus)
        {
            cout << menu->get_name() << " : " << choice++ << endl;
        }
        end_app = choice;
        cout << "Exit : " << end_app << endl;
        
        cin >> choice;
        
        if(  (choice >= 0)
           &&(choice < menus.size()))
        {
            menus[choice]->select();
        }
        else if(choice == end_app)
        {
            // set the trigger 
            trigger = signal::app_exit;
            end_application();
            return 1;
        }
        
        return 0;
    }
    // To support Observer pattern
    void update(doc_states current_state)
    {
        cout << "Application : document close() detected" << endl;
        switch(current_state)
        {
            case (doc_states::closed):
            {
                // open new document for editing by prompting the user
                if(trigger != signal::app_exit)
                {
                    prompt_to_open_new_document();
                }
                break;
            }
            default:
            {
                // do nothing
                break;
            }
        }
        return;
    }
    
    void end_application()
    {
        // do clean up
        // release back the memory resources to free store 
        for(auto &menu : menus)
        {
            menu.reset();
        }
        // close the current_doc
        current_doc.reset();
        
        cout << "Application exiting..." << endl;
        return;
    }
    
    ~Application()
    {
        cout << "Inside Application destructor" << endl;
    }
    
};

int main()
{
    Application new_app;
    int end_app = 0;
    
    while(end_app == 0)
    {
        end_app = new_app.select_menu();
    }
    
    cout << "Main Ends" << endl; 
}

The algorithms implemented for Left and right alignment make sure of two things:

1. The text aligns properly to either left or right based upon the selected strategy.

2. Without breaking in between a word (i.e. it breaks the text into next line only at spaces), it makes sure the portion of text is written to the file within the stipulated line width.

Want to see the result?

Document Editor App, Version : 4.0
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 0
Open For Read : 0
Open For Write : 1
Enter your choice : 1
Enter the file name to open for writing
Hai.txt
Document : Hai.txt is open for writing
Select alignment : Left(0), Right(1)
0
Select width :
20
Start entering your text
Enter 'END' for ending the session
Hai
Hello
How are you? Hope
you are fine!
Good seeing you
:)
END
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 1
Document : Hai.txt is closed
Application : document close() detected
Open a document for editing
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 0
Open For Read : 0
Open For Write : 1
Enter your choice : 0
Enter the file name to open for reading
Hai.txt
Document : Hai.txt is open for reading!
Hai
Hello
How
are
you?
Hope
you
are
fine!
Good
seeing
you
:)
End of File!
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 1
Document : Hai.txt is closed
Application : document close() detected
Open a document for editing
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 0
Open For Read : 0
Open For Write : 1
Enter your choice : 1
Enter the file name to open for writing
Hello.txt
Document : Hello.txt is open for writing
Select alignment : Left(0), Right(1)
0
Select width :
20
Start entering your text
Enter 'END' for ending the session
Hello How are you ? I am fine, Thanks. How are you? Am fine too. Heard that you are working on a book on the philosophy of C and C++ ? How it's going ? It's going well and keeps me engaged. Okay good to hear that. All the best for your endeavor!!!
END
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 1
Document : Hello.txt is closed
Application : document close() detected
Open a document for editing
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 0
Open For Read : 0
Open For Write : 1
Enter your choice : 1
Enter the file name to open for writing
Hello.txt
Document : Hello.txt is open for writing
Select alignment : Left(0), Right(1)
1
Select width :
20
Start entering your text
Enter 'END' for ending the session
I have been pondering about starting a blog on my understanding of the popular programming languages C and C++ for quite some time. I am not a seasoned writer. To be honest with you, I am still a little skeptical to foray into this world of blogging! I always try to understand a thing to it's very core by analyzing it, understand how it works and why it works the way it does. And once I understand these, I will articulate or rather try to teach them to my colleagues and peers. I think, I have got some good experience in that regard.
END
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 1
Document : Hello.txt is closed
Application : document close() detected
Open a document for editing
Enter your choice :
File : 0
Edit : 1
Exit : 2
2
Menu File destroyed
Menu Open destroyed
Menu_Item : Open For Read destroyed
Menu_Item : Open For Write destroyed
Menu_Item : Close destroyed
Menu Edit destroyed
Menu_Item : Copy destroyed
Menu_Item : Paste destroyed
Menu_Item : Cut destroyed
Application exiting...
Main Ends
Inside Application destructor

Let us see “Hai.txt” ( left aligned and width 20 characters per line )

Now, Let us see the left aligned portion of text saved in “Hello.txt ( width 20 characters per line selected )

Now, see the right aligned portion of text in “Hello.txt( width 20 characters per line selected )

Note: The image above is truncated at line 49 due to space limitations. Please visit my GitHub page to see the full text in ‘Hello.txt’ file

Document class and it’s open_read and open_write member functions is the client here. Through the user inputs it selects the strategy and decides either to create instance of ‘align_left_formatter’ or ‘align_right_formatter’. Client, invokes the format member function and leave the rest to the strategy pattern.

We don’t have to use std::unique_ptr while creating formatter instances as it’s scope is limited to the functions open_read and open_write. Once the format function returns, the instance is deleted, and resources are freed.

Enjoyed the chapter? Let me know in the comments below. Thanks 🙂

0

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

Chapter 15 : Series On Design Patterns – Composite Pattern

Key Words: Composite design pattern in C++

Topics at a glance:

  • Composites, containers and primitives
  • Document Editor Application is now version 3.0

Composite design pattern in C++

In this chapter we will see the use of a particularly important and widely used design pattern – composite design pattern. In this series, it is our first structural design pattern. Also, we will use one for improvising Document Reader Application.

It’s easy to explain composite pattern with a suitable problem scenario. In our Document reader 1.0 and 2.0 versions, we have added instances of menus and menu items. The idea is, a menu will always contain menu items and menu items will always have a click operation that executes one action. But in real applications menus often expands into other menus and not always menu items essentially. The same is applicable for a ‘Table’ containing sub-‘Tables’ – in which, one row of first table itself contains another sub-table with its own rows and columns. Similarly, in operating systems when you open directory it may contain files or other sub-directories. Coming back to our Application – Document editor, If you stick to concrete Menu class instances that can only contain Menu_Items its difficult to tackle this issue. The idea is to make Menu class a composite class. We have to do one more thing. i.e. to make Menu_Items and Menu class as sub-classes/derived classes of an abstract base class called Selectable. Menu class will become a container of instances of this abstract base class – Selectable. The actual instances will be either Menu instances or Menu_Items instances and both of them “is a” Selectable object as well.

Selectable class will have two pure virtual member functions – select() and get_name(). Both Menu and Menu_Items, as they are derived from Selectable must implement these interfaces select() and get_name() in their own ways. Say, Menu’s select will invoke the hover()  method while Menu_Item’s select() will invoke the click() method. The get_name() methods will simply return their associated names.

Whenever user is presented with the options, he selects one of the presented items. Item can be Menu or a Menu_Item. If it’s a Menu, then it will present another list of Selectable items, which could be either a Menu item or a Menu itself, and this goes on. Thus Menu class has become a composite class, and apparently, the primitive is the Menu_Item class. In previous versions, Menu was a container of Menu_Items. But now we uplifted Menu class as a composite which can hold abstract object’s, and manipulate them without worrying whether it’s a primitive ( a menu item ) or a composite type itself ( i.e menu ). That is the advantage of composites. Composite helps in representing containers and their primitive data types as an abstract object. So, when coding to a composite object instance, programmer does not need to worry whether he is coding to a container instance or a primitive instance.

That is the philosophy behind composite pattern. i.e. to abstract container and its primitives.

Here, in our example, Document Editor Application is at version 3.0, with facility to add sub-menus when opening a file. i.e. whether to open in read only mode or write mode. File menu presents with options to Open, Close. Open presents with options – Open for Read, Open for Write.

Let us see version 3.0 of or Document Editor application improvised with composite pattern.

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
    virtual ~Command(){}
    virtual void execute() = 0;
};

class Selectable
{
public:
    virtual ~Selectable(){}
    virtual string get_name() = 0;
    virtual void select() = 0;    
};

class Menu_Item : public Selectable
{
private:
    string name_;
    unique_ptr<Command> command_object_;
public:
    Menu_Item(const char* const name) : 
        name_{name},
        command_object_{nullptr}
    {
    }
    
    ~Menu_Item()
    {
        cout << "Menu_Item : " << name_ << " destroyed" << endl;
        command_object_.reset();        
    }
    
    void add_command_object(Command *command_object)
    {
        command_object_.reset(command_object);
    }
    
    void select() override
    {
        click();
    }
    
    int click()
    {
        int status = -1;
        if(command_object_ != nullptr)
        {
            command_object_->execute();
            status = 0;
        }
        
        return status;
    }
    
    string get_name()
    {
        return name_;
    }
};

class Menu : public Selectable
{
private:
    string name_;
    vector<unique_ptr<Selectable>> menu_items;
public:
    Menu(const char* const name): 
        name_{name}
    {
        
    }
    
    ~Menu()
    {
        cout << "Menu " << name_ << " destroyed" << endl;
        for (auto &item : menu_items)
        {
            item.reset();
        }
    }
    
    void add_menu_item(unique_ptr<Selectable> menu_item)
    {
        menu_items.push_back(std::move(menu_item));
    }
    
    void select() override 
    {
        hover();
    }
    
    void hover()
    {
        int choice;
        int count = 0;
        
        for (auto &item : menu_items)
        {
            cout << item->get_name() << " : " << count++ << endl;
        }
        
        cout << "Enter your choice : ";
        cin >> choice;
        
        if(   (choice >= 0) 
            &&(choice <= menu_items.size()) )
        {
            menu_items[choice]->select();
            return;
        }
        
    }
    
    string get_name()
    {
        return name_;
    }
    
};

class Subject;

enum class doc_states{created = 0, opened, closed, changed, moved};

class Observer
{
public:
    ~Observer(){}
    virtual void update(doc_states current_state) = 0;
};

class Subject
{
public:
    ~Subject(){}
    virtual void notify() = 0;
    virtual void attach(Observer *this_observer) = 0;
    virtual void dettach(Observer *this_observer) = 0;
};

class Document : public Subject 
{
private:
    string name_;
    doc_states current_state;
    vector<Observer*> observers;    
public:
    typedef void (Document::*doc_function)();
    Document(): current_state{doc_states::created}
    { 
        /* do nothing */ 
    }
    
    ~Document()
    {
        if(   (current_state != doc_states::closed)
            &&(current_state != doc_states::created) )
        {
            // i.e. a document is open. So close it.
            close();
        }
    }
    
    void open_read()
    {
        cout << "Enter the file name to open for reading" << endl;
        cin >> name_;
        current_state = doc_states::opened;
        cout << "Document : " << name_ << " is open for reading" << endl;
    }
    
     void open_write()
    {
        cout << "Enter the file name to open for writing" << endl;
        cin >> name_;
        current_state = doc_states::opened;
        cout << "Document : " << name_ << " is open for writing" << endl;
    }
    
    void close()
    {
       if(   (current_state != doc_states::closed)
            &&(current_state != doc_states::created) )
        {
            current_state = doc_states::closed;
            cout << "Document : " << name_ << " is closed" << endl;
            name_.clear();
            // notify the subscribed observers 
            notify();
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void copy(int start, int end, string &copied_text)
    void copy()
    {
        if(current_state != doc_states::closed)
        {
            cout << "copying..." << endl;
            // copy from start to end to copied_text
            cout << "copy complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void paste(int position, string& insert_text)
    void paste()
    {
        if(current_state != doc_states::closed)
        {
            cout << "pasting..." << endl;
            cout << "pasting complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void cut(int start, int end, string &extracted_text)
    void cut()
    {
        if(current_state != doc_states::closed)
        {
            cout << "cutting..." << endl;
            // copy from start to end to copied_text
            cout << "cut complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    // support for Observer pattern
    void notify() override
    {
        for( auto &observer : observers )
        {
            observer->update(current_state);
        }
    }
    
    void attach(Observer *this_observer) override
    {
        observers.push_back(this_observer);
    }
    
    void dettach(Observer *this_observer) override
    {
        int position = 0;
        auto begin = observers.begin();
        auto end = observers.end();
        
        for( auto itr = begin; itr < end; ++itr )
        {
            auto obs = *itr;
            if(this_observer == obs)
            {
                observers.erase(itr);
            }
        }
    }
    
};

class Open_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Open_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Close_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Close_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Copy_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Copy_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Paste_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Paste_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Cut_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Cut_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Application : public Observer
{
private:
    vector<unique_ptr<Menu>> menus;
    shared_ptr<Document> current_doc;
    enum class signal{app_launched = 0, app_exit, user_interrupt, other, no_trigger};
    signal trigger;
    
    void prompt_to_open_new_document()
    {
        // prompt the user 
        cout << "Open a document for editing" << endl;        
        return;
    }
    
public:
    Application()
    {
        string doc_name;
        trigger = signal::app_launched;
        
        cout << "Document Editor App, Version : 3.0" << endl;
        
        shared_ptr<Document> document = make_shared<Document>();
        current_doc = document;

        // Attach this 'Application' as an Observer of document 
        (document.get())->attach(this);
        
        // add Menus "File" and "Edit"
        unique_ptr<Menu> file_menu = make_unique<Menu>("File");
        unique_ptr<Menu> open_sub_menu = make_unique<Menu>("Open");
        unique_ptr<Menu_Item> open_menu_item_1 = make_unique<Menu_Item>("Open For Read");
        unique_ptr<Menu_Item> open_menu_item_2 = make_unique<Menu_Item>("Open For Write");
        
        
        Open_Document_Command *open_read_cmd_object = new Open_Document_Command(document, &Document::open_read);
        Open_Document_Command *open_write_cmd_object = new Open_Document_Command(document, &Document::open_write);
        open_menu_item_1->add_command_object(open_read_cmd_object);
        open_menu_item_2->add_command_object(open_write_cmd_object);
        
        open_sub_menu->add_menu_item(std::move(open_menu_item_1));
        open_sub_menu->add_menu_item(std::move(open_menu_item_2));
        
        file_menu->add_menu_item(std::move(open_sub_menu));
        
        unique_ptr<Menu_Item> close_item = make_unique<Menu_Item> ("Close");
        Close_Document_Command *close_cmd_object = new Close_Document_Command(document, &Document::close);
        close_item->add_command_object(close_cmd_object);
        
        file_menu->add_menu_item(std::move(close_item));
        
        unique_ptr<Menu> edit_menu = make_unique<Menu>("Edit");
        unique_ptr<Menu_Item> copy_item = make_unique<Menu_Item>("Copy");
        Copy_Document_Command *copy_cmd_object = new Copy_Document_Command(document, &Document::copy);
        copy_item->add_command_object(copy_cmd_object);
        
        edit_menu->add_menu_item(std::move(copy_item));
        
        unique_ptr<Menu_Item> paste_item = make_unique<Menu_Item>("Paste");
        Paste_Document_Command *paste_cmd_object = new Paste_Document_Command(document, &Document::paste);
        paste_item->add_command_object(paste_cmd_object);
        
        edit_menu->add_menu_item(std::move(paste_item));
        
        unique_ptr<Menu_Item> cut_item = make_unique<Menu_Item>("Cut");
        Cut_Document_Command *cut_cmd_object = new Cut_Document_Command(document, &Document::cut);
        cut_item->add_command_object(cut_cmd_object);
        
        edit_menu->add_menu_item(std::move(cut_item));
        
        menus.push_back(std::move(file_menu));
        menus.push_back(std::move(edit_menu));
    }
    
    int select_menu()
    {
        int choice = 0;
        int end_app = 0;
        cout << "Enter your choice : " << endl;
        for(auto &menu : menus)
        {
            cout << menu->get_name() << " : " << choice++ << endl;
        }
        end_app = choice;
        cout << "Exit : " << end_app << endl;
        
        cin >> choice;
        
        if(  (choice >= 0)
           &&(choice < menus.size()))
        {
            menus[choice]->select();
        }
        else if(choice == end_app)
        {
            // set the trigger 
            trigger = signal::app_exit;
            end_application();
            return 1;
        }
        
        return 0;
    }
    // To support Observer pattern
    void update(doc_states current_state)
    {
        cout << "Application : document close() detected" << endl;
        switch(current_state)
        {
            case (doc_states::closed):
            {
                // open new document for editing by prompting the user
                if(trigger != signal::app_exit)
                {
                    prompt_to_open_new_document();
                }
                break;
            }
            default:
            {
                // do nothing
                break;
            }
        }
        return;
    }
    
    void end_application()
    {
        // do clean up
        // release back the memory resources to free store 
        for(auto &menu : menus)
        {
            menu.reset();
        }
        // close the current_doc
        current_doc.reset();
        
        cout << "Application exiting..." << endl;
        return;
    }
    
    ~Application()
    {
        cout << "Inside Application destructor" << endl;
    }
    
};

int main()
{
    Application new_app;
    int end_app = 0;
    
    while(end_app == 0)
    {
        end_app = new_app.select_menu();
    }
    
    cout << "Main Ends" << endl; 
}

Want to see the composite pattern in action?

Document Editor App, Version : 3.0
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 0
Open For Read : 0
Open For Write : 1
Enter your choice : 0
Enter the file name to open for reading
Hello
Document : Hello is open for reading
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 1
Document : Hello is closed
Application : document close() detected
Open a document for editing
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 0
Open For Read : 0
Open For Write : 1
Enter your choice : 1
Enter the file name to open for writing
hello
Document : hello is open for writing
Enter your choice :
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your choice : 1
Document : hello is closed
Application : document close() detected
Open a document for editing
Enter your choice :
File : 0
Edit : 1
Exit : 2
2
Menu File destroyed
Menu Open destroyed
Menu_Item : Open For Read destroyed
Menu_Item : Open For Write destroyed
Menu_Item : Close destroyed
Menu Edit destroyed
Menu_Item : Copy destroyed
Menu_Item : Paste destroyed
Menu_Item : Cut destroyed
Application exiting...
Main Ends
Inside Application destructor

As discussed, above when user selected open, it is not a menu item as in the previous versions 1.0 and 2.0. Open itself is a Menu or rather we can call it a sub-menu and it presented user with menu items – “Open For Read”, “Open For Write”. User could select either of these options and henceforth use the Document Editor Application as usual.

Enjoyed the chapter? Let me know in the comment below. Thanks 🙂

0

Chapter 14 : Series On Design Patterns – Observer Pattern

Key Words: Observer pattern in C++

Topics at a glance:

  • Let us observe a subject closely
  • how we can improvise our Document editor application with observer pattern
  • Document editor application is now 2.0 !

In previous chapter we saw command behavioral pattern. In this chapter we will improvise our Application – Document editor using Observer pattern.

Observer pattern in C++

Observer in simple terms is a contract between two entities called Subject and Observer. Its core philosophy is created on a subscription-based notification model, where the observers are the subscribers who are interested in the changes happening in a subject.

In the Document editor, you might have seen that whenever a document is closed, it will notify the application. But in the previous version presented in Chapter 13, I used basic object pointer (pointer to Application) and a function call back using member function pointer. The purpose was to notify the application about a document close. What all things to do when the subscriber receives such a notification depends on the use-case. Say in our Application class, Application can prompt the user to re-open the closed document or open another new document for editing and so on.

This use-case where observers are notified by the subject about any of its specific updates could be generalized and designed with the help of Observer Design pattern. So, here we are making subjects and its observers as objects. Subject instances must implement the interfaces such as notify(), attach(Observer *) and detach(Observer *) , while the Observer instances must implement the interface update(current_state val). This is like a contract between the subject and the observers and unlike chapter 13’s implementation of a similar functionality, here we can subscribe, unsubscribe and get updates. Also, in Observer pattern we build this framework on top of object instances rather than isolated call back functions.

Now, let us see how we can improvise our Document editor application with the help of Observer pattern. Here the Observer is Application instance and is interested only when, the state of it subject i.e. Document instance changes to closed. When Application instance is notified about a document close, it prompts the user to open a new document.

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
    virtual ~Command(){}
    virtual void execute() = 0;
};

class Menu_Item
{
private:
    unique_ptr<Command> command_object_;
    string name_;
public:
    Menu_Item(const char* const name) : 
        name_{name},
        command_object_{nullptr}
    {
    }
    
    ~Menu_Item()
    {
        cout << "Menu_Item : " << name_ << " destroyed" << endl;
        command_object_.reset();        
    }
    
    void add_command_object(Command *command_object)
    {
        command_object_.reset(command_object);
    }
    
    int click()
    {
        int status = -1;
        if(command_object_ != nullptr)
        {
            command_object_->execute();
            status = 0;
        }
        
        return status;
    }
    
    string& get_name()
    {
        return name_;
    }
};

class Menu
{
private:
    string name_;
    vector<unique_ptr<Menu_Item>> menu_items;
public:
    Menu(const char* const name): 
        name_{name}
    {
        
    }
    
    ~Menu()
    {
        cout << "Menu " << name_ << " destroyed" << endl;
        for (auto &item : menu_items)
        {
            item.reset();
        }
    }
    
    void add_menu_item(unique_ptr<Menu_Item> menu_item)
    {
        menu_items.push_back(std::move(menu_item));
    }
    
    int hover()
    {
        int choice;
        int count = 0;
        
        for (auto &item : menu_items)
        {
            cout << item->get_name() << " : " << count++ << endl;
        }
        
        cout << "Enter your Menu Item choice : ";
        cin >> choice;
        
        if(   (choice >= 0) 
            &&(choice <= menu_items.size()) )
        {
            return menu_items[choice]->click();
        }
        
    }
    
    // overloaded version 
    // used for mimicking a menu item click by the application  
    int hover(int choice)
    {
        return menu_items[choice]->click();
    }
    
    string& get_name()
    {
        return name_;
    }
    
};

class Subject;

enum class doc_states{created = 0, opened, closed, changed, moved};

class Observer
{
public:
    ~Observer(){}
    virtual void update(doc_states current_state) = 0;
};

class Subject
{
public:
    ~Subject(){}
    virtual void notify() = 0;
    virtual void attach(Observer *this_observer) = 0;
    virtual void dettach(Observer *this_observer) = 0;
};

class Document : public Subject 
{
private:
    string name_;
    doc_states current_state;
    vector<Observer*> observers;    
public:
    typedef void (Document::*doc_function)();
    Document(): current_state{doc_states::created}
    { 
        /* do nothing */ 
    }
    
    ~Document()
    {
        if(   (current_state != doc_states::closed)
            &&(current_state != doc_states::created) )
        {
            // i.e. a document is open. So close it.
            close();
        }
    }
    
    void open()
    {
        cout << "Enter the file name to open" << endl;
        cin >> name_;
        current_state = doc_states::opened;
        cout << "Document : " << name_ << " is open" << endl;
    }
    
    void close()
    {
       if(   (current_state != doc_states::closed)
            &&(current_state != doc_states::created) )
        {
            current_state = doc_states::closed;
            cout << "Document : " << name_ << " is closed" << endl;
            name_.clear();
            // notify the subscribed observers 
            notify();
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void copy(int start, int end, string &copied_text)
    void copy()
    {
        if(current_state != doc_states::closed)
        {
            cout << "copying..." << endl;
            // copy from start to end to copied_text
            cout << "copy complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void paste(int position, string& insert_text)
    void paste()
    {
        if(current_state != doc_states::closed)
        {
            cout << "pasting..." << endl;
            cout << "pasting complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    //void cut(int start, int end, string &extracted_text)
    void cut()
    {
        if(current_state != doc_states::closed)
        {
            cout << "cutting..." << endl;
            // copy from start to end to copied_text
            cout << "cut complete" << endl;
        }
        else
        {
            cout << "No documents are open" << endl;
        }
    }
    
    // support for Observer pattern
    void notify() override
    {
        for( auto &observer : observers )
        {
            observer->update(current_state);
        }
    }
    
    void attach(Observer *this_observer) override
    {
        observers.push_back(this_observer);
    }
    
    void dettach(Observer *this_observer) override
    {
        int position = 0;
        auto begin = observers.begin();
        auto end = observers.end();
        
        for( auto itr = begin; itr < end; ++itr )
        {
            auto obs = *itr;
            if(this_observer == obs)
            {
                observers.erase(itr);
            }
        }
    }
    
};

class Open_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Open_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Close_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Close_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Copy_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Copy_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Paste_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Paste_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Cut_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Cut_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Application : public Observer
{
private:
    vector<unique_ptr<Menu>> menus;
    shared_ptr<Document> current_doc;
    enum class signal{app_launched = 0, app_exit, user_interrupt, other, no_trigger};
    signal trigger;
    
    void prompt_to_open_new_document()
    {
        // prompt the user 
        cout << "Open a document for editing" << endl;        
        return;
    }
    
public:
    Application()
    {
        string doc_name;
        trigger = signal::app_launched;
        
        cout << "Document Editor App, Version : 2.0" << endl;
        
        shared_ptr<Document> document = make_shared<Document>();
        current_doc = document;

        // Attach this 'Application' as an Observer of document 
        (document.get())->attach(this);
        
        // 2. add Menus "File" and "Edit"
        unique_ptr<Menu> file_menu = make_unique<Menu>("File");
        unique_ptr<Menu_Item> open_item = make_unique<Menu_Item>("Open");
        Open_Document_Command *open_cmd_object = new Open_Document_Command(document, &Document::open);
        open_item->add_command_object(open_cmd_object);
        
        file_menu->add_menu_item(std::move(open_item));
        
        unique_ptr<Menu_Item> close_item = make_unique<Menu_Item> ("Close");
        Close_Document_Command *close_cmd_object = new Close_Document_Command(document, &Document::close);
        close_item->add_command_object(close_cmd_object);
        
        file_menu->add_menu_item(std::move(close_item));
        
        unique_ptr<Menu> edit_menu = make_unique<Menu>("Edit");
        unique_ptr<Menu_Item> copy_item = make_unique<Menu_Item>("Copy");
        Copy_Document_Command *copy_cmd_object = new Copy_Document_Command(document, &Document::copy);
        copy_item->add_command_object(copy_cmd_object);
        
        edit_menu->add_menu_item(std::move(copy_item));
        
        unique_ptr<Menu_Item> paste_item = make_unique<Menu_Item>("Paste");
        Paste_Document_Command *paste_cmd_object = new Paste_Document_Command(document, &Document::paste);
        paste_item->add_command_object(paste_cmd_object);
        
        edit_menu->add_menu_item(std::move(paste_item));
        
        unique_ptr<Menu_Item> cut_item = make_unique<Menu_Item>("Cut");
        Cut_Document_Command *cut_cmd_object = new Cut_Document_Command(document, &Document::cut);
        cut_item->add_command_object(cut_cmd_object);
        
        edit_menu->add_menu_item(std::move(cut_item));
        
        menus.push_back(std::move(file_menu));
        menus.push_back(std::move(edit_menu));
    }
    
    int select_menu()
    {
        int choice = 0;
        int end_app = 0;
        cout << "Enter your Menu choice" << endl;
        for(auto &menu : menus)
        {
            cout << menu->get_name() << " : " << choice++ << endl;
        }
        end_app = choice;
        cout << "Exit : " << end_app << endl;
        
        cin >> choice;
        
        if(  (choice >= 0)
           &&(choice < menus.size()))
        {
            menus[choice]->hover();
        }
        else if(choice == end_app)
        {
            // set the trigger 
            trigger = signal::app_exit;
            end_application();
            return 1;
        }
        
        return 0;
    }
    // To support Observer pattern
    void update(doc_states current_state)
    {
        cout << "Application : document close() detected" << endl;
        switch(current_state)
        {
            case (doc_states::closed):
            {
                // open new document for editing by prompting the user
                if(trigger != signal::app_exit)
                {
                    prompt_to_open_new_document();
                }
                break;
            }
            default:
            {
                // do nothing
                break;
            }
        }
        return;
    }
    
    void end_application()
    {
        // do clean up
        // release back the memory resources to free store 
        for(auto &menu : menus)
        {
            menu.reset();
        }
        // close the current_doc
        current_doc.reset();
        
        cout << "Application exiting..." << endl;
        return;
    }
    
    ~Application()
    {
        cout << "Inside Application destructor" << endl;
    }
    
};

int main()
{
    Application new_app;
    int end_app = 0;
    
    while(end_app == 0)
    {
        end_app = new_app.select_menu();
    }
    
    cout << "Main Ends" << endl; 
}

Want to see this code in action?

Document Editor App, Version : 2.0
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your Menu Item choice : 0
Enter the file name to open
hello
Document : hello is open
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your Menu Item choice : 1
Document : hello is closed
Application : document close() detected
Open a document for editing
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
2
Menu File destroyed
Menu_Item : Open destroyed
Menu_Item : Close destroyed
Menu Edit destroyed
Menu_Item : Copy destroyed
Menu_Item : Paste destroyed
Menu_Item : Cut destroyed
Application exiting...
Main Ends
Inside Application destructor

Observer in action:

Here you can see that when the User choose to close the document, application gets notified and prompts the user to open a file to edit. Finally, when User chooses to exit, Application does the clean-up as usual.

Other improvisations.

This document editor app (version 2.0) also fixed the issue with user selecting an operation when no documents are open or try to close a document which is not open. Want to see these features?

Document Editor App, Version : 2.0
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your Menu Item choice : 1
No documents are open
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your Menu Item choice : 0
Enter the file name to open
hello
Document : hello is open
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your Menu Item choice : 1
Document : hello is closed
Application : document close() detected
Open a document for editing
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
1
Copy : 0
Paste : 1
Cut : 2
Enter your Menu Item choice : 0
No documents are open
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
2
Menu File destroyed
Menu_Item : Open destroyed
Menu_Item : Close destroyed
Menu Edit destroyed
Menu_Item : Copy destroyed
Menu_Item : Paste destroyed
Menu_Item : Cut destroyed
Application exiting...
Main Ends
Inside Application destructor

You can see that the Application tells the User that “No documents are open” when any operation is invoked when no document is already open.

Enjoyed the chapter. Let me know in the comments below. Thanks 🙂

0

Chapter 13 : Series On Design Patterns – Command Pattern

Key Words: Behavioral design pattern, command design pattern in C++

Topics at a glance:

  • Understanding a behavioral design pattern with command pattern
  • How we implemented command in classic C++ (C++ 98) way
  • How we should implement command in modern C++ way.
  • Let us understand how GUI elements like Menu and Menu Items work, and how a button click execute actions?

Behavioral design pattern: Command design pattern in C++

In this chapter we will see how to implement command design pattern, which is a behavioral pattern, in modern C++. I won’t be talking much about command pattern itself. But, I will point out the important aspects of command pattern.

Command pattern’s core is an abstract base class which declares at least one pure virtual member function called execute(). One has to define concrete derived classes that actually implements this method execute(). The instances of such concrete classes are called as command objects. Command objects abstract the command/request itself into objects. That is the philosophy behind command pattern. The one who raises the request only knows when to raise one command/request, but will be completely unaware of which object instances of what class types are going to actually execute the request and what all resources are required for executing this command. So, the requester simply initiates a command. This command will be first received by the command object as the initiator invokes this execute method. Command object then delegates the request to the actual object that realises this functionality by invoking the call back member function on that final receiver object.

For implementing the execute method, command objects require the following information:

  1. The object instance that implements the actual functionality to realize the command
  2. The method that should be invoked on the object instance
  3. The parameters, if any, that must be passed into that method to execute it correctly

Command objects must be configured with these information, either at time of construction or at least before invoking the execute().

Basically command object elegantly hides the object/entity that initiates a command/request from the object that realizes the command. That said, command object works as an intermediary object between the initiator and the realizer, by making requests into objects.

Now, I will demonstrate the C++ implementation of a typical command pattern, taking the following example:

A document editor application that provides the user with menu-based interface to execute the commands such as opening a document, editing a document (cut, paste, copy) and finally closing the document and exiting the application. This is designed based on command pattern.

The main components of this design are:

  1. Application class
  2. Document class
  3. Menu class
  4. Menu Item class
  5. Command class (abstract base class with a pure virtual execute() method)
  6. Concrete derived classes for releasing the commands open, close, edit:copy, paste, cut

Let us see how this is implemented in classic C++ (C++ 98 or before modern C++).

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
    virtual ~Command(){}
    virtual void execute() = 0;
};

class Menu_Item
{
private:
    Command *command_object_;
    string name_;
public:
    Menu_Item(const char* const name) : name_{name}
    {
        command_object_ = nullptr;
    }
    
    void add_command_object(Command *command_object)
    {
        command_object_ = command_object;
    }
    
    int click()
    {
        int status = -1;
        if(command_object_ != nullptr)
        {
            command_object_->execute();
            status = 0;
        }
        
        return status;
    }
    
    string& get_name()
    {
        return name_;
    }
};

class Menu
{
private:
    string name_;
    vector<Menu_Item*> menu_items;
public:
    Menu(const char* const name): 
        name_{name}
    {
        
    }
    
    void add_menu_item(Menu_Item *menu_item)
    {
        menu_items.push_back(menu_item);
    }
    
    int hover()
    {
        int choice;
        int count = 0;
        
        for (auto item : menu_items)
        {
            cout << item->get_name() << " : " << count++ << endl;
        }
        
        cout << "Enter your Menu Item choice : ";
        cin >> choice;
        
        if(   (choice >= 0) 
            &&(choice <= menu_items.size()) )
        {
            return menu_items[choice]->click();
        }
        
    }
    
    string& get_name()
    {
        return name_;
    }
    
};

class Document
{
private:
    string name_;
public:
    typedef void (Document::*doc_function)();
    Document(const char* const name): name_{name}{}
    
    ~Document()
    {
        close();
    }
    
    void open()
    {
        cout << "Document : " << name_ << " is open" << endl;
    }
    
    void close()
    {
        cout << "Document : " << name_ << " is closed" << endl;
    }
    
    //void copy(int start, int end, string &copied_text)
    void copy()
    {
        cout << "copying..." << endl;
        // copy from start to end to copied_text
        cout << "copy complete" << endl;
    }
    
    //void paste(int position, string& insert_text)
    void paste()
    {
        cout << "pasting..." << endl;
        cout << "pasting complete" << endl;
    }
    
    //void cut(int start, int end, string &extracted_text)
    void cut()
    {
        cout << "cutting..." << endl;
        // copy from start to end to copied_text
        cout << "cut complete" << endl;
    }
    
};

class Open_Document_Command : public Command
{
private:
    Document* document_;
    Document::doc_function function;

public:
    Open_Document_Command(Document* document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_->*function)();
    }
};

class Close_Document_Command : public Command
{
private:
    Document* document_;
    Document::doc_function function;

public:
    Close_Document_Command(Document* document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_->*function)();
    }
};


class Copy_Document_Command : public Command
{
private:
    Document* document_;
    Document::doc_function function;

public:
    Copy_Document_Command(Document* document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_->*function)();
    }
};

class Paste_Document_Command : public Command
{
private:
    Document* document_;
    Document::doc_function function;

public:
    Paste_Document_Command(Document* document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_->*function)();
    }
};

class Cut_Document_Command : public Command
{
private:
    Document* document_;
    Document::doc_function function;

public:
    Cut_Document_Command(Document* document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    
    void execute() override
    {
        (document_->*function)();
    }
};


class Application
{
private:
    vector<Menu*> menus;
    Document *document;
    Menu *file_menu;
    Menu *edit_menu;
    Menu_Item *open_item;
    Menu_Item *close_item;
    Menu_Item *paste_item;
    Menu_Item *copy_item;
    Menu_Item *cut_item;
    Open_Document_Command *open_cmd_object;
    Close_Document_Command *close_cmd_object;
    Paste_Document_Command *paste_cmd_object;
    Copy_Document_Command *copy_cmd_object;
    Cut_Document_Command *cut_cmd_object;
    
public:
    Application()
    {
        string doc_name;
        
        cout << "Document editor app" << endl;
        // 1. open a document first for editing 
        cout << "Enter the file name to edit" << endl;
        cin >> doc_name;
        
        document = new Document(doc_name.c_str());
        
        // 2. add Menus "File" and "Edit"
        file_menu = new Menu("File");
        open_item = new Menu_Item("Open");
        open_cmd_object = new Open_Document_Command(document, &Document::open);
        open_item->add_command_object(open_cmd_object);
        
        file_menu->add_menu_item(open_item);
        
        close_item = new Menu_Item("Close");
        close_cmd_object = new Close_Document_Command(document, &Document::close);
        close_item->add_command_object(close_cmd_object);
        
        file_menu->add_menu_item(close_item);
        
        edit_menu = new Menu("Edit");
        copy_item = new Menu_Item("Copy");
        copy_cmd_object = new Copy_Document_Command(document, &Document::copy);
        copy_item->add_command_object(copy_cmd_object);
        
        edit_menu->add_menu_item(copy_item);
        
        paste_item = new Menu_Item("Paste");
        paste_cmd_object = new Paste_Document_Command(document, &Document::paste);
        paste_item->add_command_object(paste_cmd_object);
        
        edit_menu->add_menu_item(paste_item);
        
        cut_item = new Menu_Item("Cut");
        cut_cmd_object = new Cut_Document_Command(document, &Document::cut);
        cut_item->add_command_object(cut_cmd_object);
        
        edit_menu->add_menu_item(cut_item);
        
        menus.push_back(file_menu);
        menus.push_back(edit_menu);
    }
    
    int select_menu()
    {
        int choice = 0;
        int end_app = 0;
        cout << "Enter your Menu choice" << endl;
        for(auto menu : menus)
        {
            cout << menu->get_name() << " : " << choice++ << endl;
        }
        end_app = choice;
        cout << "Exit : " << end_app << endl;
        
        cin >> choice;
        
        if(  (choice >= 0)
           &&(choice < menus.size()))
        {
            menus[choice]->hover();
        }
        else if(choice == end_app)
        {
            end_application();
            return 1;
        }
        
        return 0;
    }
    
    void end_application()
    {
        // Delete the items in the reverse order  
        // How to close the document ?
        // 2. delete the command_objects created 
        delete open_cmd_object;
        delete close_cmd_object;
        delete paste_cmd_object;
        delete copy_cmd_object;
        delete cut_cmd_object;
        // 3. Delete menu items created 
        delete open_item;
        delete close_item;
        delete paste_item;
        delete copy_item;
        delete cut_item;
        // 4. delete menus 
        delete file_menu;
        delete edit_menu;
        // 5. finally delete document
        delete document;
        
        cout << "Application exiting..." << endl;
    }
    
};

Here, everything is proper. But did you observe that in the end it is essential to delete all the new created objects from free store. This look untidy and not at all is complying to the recommended modern C++ way. Let us use the elegant destructors and std::unique_ptrs and std::shared_ptrs for doing this in the modern C++ way.

  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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#include <iostream>
#include <string>
#include <memory>
#include <vector>

using namespace std;

class Command
{
public:
    virtual ~Command(){}
    virtual void execute() = 0;
};

class Menu_Item
{
private:
    unique_ptr<Command> command_object_;
    string name_;
public:
    Menu_Item(const char* const name) : 
        name_{name},
        command_object_{nullptr}
    {
    }
    
    ~Menu_Item()
    {
        cout << "Menu_Item : " << name_ << " destroyed" << endl;
        command_object_.reset();        
    }
    
    void add_command_object(Command *command_object)
    {
        command_object_.reset(command_object);
    }
    
    int click()
    {
        int status = -1;
        if(command_object_ != nullptr)
        {
            command_object_->execute();
            status = 0;
        }
        
        return status;
    }
    
    string& get_name()
    {
        return name_;
    }
};

class Menu
{
private:
    string name_;
    vector<unique_ptr<Menu_Item>> menu_items;
public:
    Menu(const char* const name): 
        name_{name}
    {
        
    }
    
    ~Menu()
    {
        cout << "Menu " << name_ << " destroyed" << endl;
        for (auto &item : menu_items)
        {
            item.reset();
        }
    }
    
    void add_menu_item(unique_ptr<Menu_Item> menu_item)
    {
        menu_items.push_back(std::move(menu_item));
    }
    
    int hover()
    {
        int choice;
        int count = 0;
        
        for (auto &item : menu_items)
        {
            cout << item->get_name() << " : " << count++ << endl;
        }
        
        cout << "Enter your Menu Item choice : ";
        cin >> choice;
        
        if(   (choice >= 0) 
            &&(choice <= menu_items.size()) )
        {
            return menu_items[choice]->click();
        }
        
    }
    
    // overloaded version 
    // used for mimicking a menu item click by the application  
    int hover(int choice)
    {
        return menu_items[choice]->click();
    }
    
    string& get_name()
    {
        return name_;
    }
    
};


class Application;

class Document
{
private:
    string name_;
    Application *app_;
    void (Application::*notify)();
public:
    typedef void (Document::*doc_function)();
    Document( const char* const name, Application *app, void (Application::*app_notify)() ): 
    name_{name}, 
    app_{app},
    notify{app_notify}
    { 
        /* do nothing */ 
    }
    
    ~Document()
    {
        close();
    }
    
    void open()
    {
        cout << "Document : " << name_ << " is open" << endl;
    }
    
    void close()
    {
        cout << "Document : " << name_ << " is closed" << endl;
        // notify the application 
        (app_->*notify)();
        
    }
    
    //void copy(int start, int end, string &copied_text)
    void copy()
    {
        cout << "copying..." << endl;
        // copy from start to end to copied_text
        cout << "copy complete" << endl;
    }
    
    //void paste(int position, string& insert_text)
    void paste()
    {
        cout << "pasting..." << endl;
        cout << "pasting complete" << endl;
    }
    
    //void cut(int start, int end, string &extracted_text)
    void cut()
    {
        cout << "cutting..." << endl;
        // copy from start to end to copied_text
        cout << "cut complete" << endl;
    }
    
};

class Open_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Open_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Close_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Close_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Copy_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Copy_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Paste_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Paste_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    void execute() override
    {
        (document_.get()->*function)();
    }
};

class Cut_Document_Command : public Command
{
private:
    shared_ptr<Document> document_;
    Document::doc_function function;

public:
    Cut_Document_Command(shared_ptr<Document> document, Document::doc_function call_back):
        document_{document}, function{call_back}
    {
        
    }
    
    void execute() override
    {
        (document_.get()->*function)();
    }
};


class Application
{
private:
    vector<unique_ptr<Menu>> menus;
    enum class signal{app_launched = 0, app_exit, user_interrupt, other, no_trigger};
    signal trigger;
public:
    Application()
    {
        string doc_name;
        trigger = signal::app_launched;
        
        cout << "Document editor app" << endl;
        // 1. open a document first for editing 
        cout << "Enter the file name to edit" << endl;
        cin >> doc_name;
        
        shared_ptr<Document> document = make_shared<Document>(doc_name.c_str(), this, &Application::notify_doc_close);
        
        // 2. add Menus "File" and "Edit"
        unique_ptr<Menu> file_menu = make_unique<Menu>("File");
        unique_ptr<Menu_Item> open_item = make_unique<Menu_Item>("Open");
        Open_Document_Command *open_cmd_object = new Open_Document_Command(document, &Document::open);
        open_item->add_command_object(open_cmd_object);
        
        file_menu->add_menu_item(std::move(open_item));
        
        unique_ptr<Menu_Item> close_item = make_unique<Menu_Item> ("Close");
        Close_Document_Command *close_cmd_object = new Close_Document_Command(document, &Document::close);
        close_item->add_command_object(close_cmd_object);
        
        file_menu->add_menu_item(std::move(close_item));
        
        unique_ptr<Menu> edit_menu = make_unique<Menu>("Edit");
        unique_ptr<Menu_Item> copy_item = make_unique<Menu_Item>("Copy");
        Copy_Document_Command *copy_cmd_object = new Copy_Document_Command(document, &Document::copy);
        copy_item->add_command_object(copy_cmd_object);
        
        edit_menu->add_menu_item(std::move(copy_item));
        
        unique_ptr<Menu_Item> paste_item = make_unique<Menu_Item>("Paste");
        Paste_Document_Command *paste_cmd_object = new Paste_Document_Command(document, &Document::paste);
        paste_item->add_command_object(paste_cmd_object);
        
        edit_menu->add_menu_item(std::move(paste_item));
        
        unique_ptr<Menu_Item> cut_item = make_unique<Menu_Item>("Cut");
        Cut_Document_Command *cut_cmd_object = new Cut_Document_Command(document, &Document::cut);
        cut_item->add_command_object(cut_cmd_object);
        
        edit_menu->add_menu_item(std::move(cut_item));
        
        
        menus.push_back(std::move(file_menu));
        menus.push_back(std::move(edit_menu));
    }
    
    int select_menu()
    {
        int choice = 0;
        int end_app = 0;
        cout << "Enter your Menu choice" << endl;
        for(auto &menu : menus)
        {
            cout << menu->get_name() << " : " << choice++ << endl;
        }
        end_app = choice;
        cout << "Exit : " << end_app << endl;
        
        cin >> choice;
        
        if(  (choice >= 0)
           &&(choice < menus.size()))
        {
            menus[choice]->hover();
        }
        else if(choice == end_app)
        {
            // set the trigger 
            trigger = signal::app_exit;
            end_application();
            return 1;
        }
        
        return 0;
    }
    
    void notify_doc_close()
    {
        cout << "Application : document close() detected" << endl;
        
        if(trigger != signal::app_exit) // not supporting re-opening or opening another document 
        {
            end_application();
        }
    }
    
    void end_application()
    {
        // do clean up
        // release back the memory resources to free store 
        for(auto &menu : menus)
        {
            menu.reset();
        }
        
        cout << "Application exiting..." << endl;
    }
    
};

int main()
{
    Application new_app;
    int end_app = 0;
    
    while(end_app == 0)
    {
        end_app = new_app.select_menu();
    }
    
    cout << "Main Ends" << endl; 
}

Want to see the result?

Document editor app
Enter the file name to edit
hello
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
0
Open : 0
Close : 1
Enter your Menu Item choice : 0
Document : hello is open
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
1
Copy : 0
Paste : 1
Cut : 2
Enter your Menu Item choice : 0
copying...
copy complete
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
1
Copy : 0
Paste : 1
Cut : 2
Enter your Menu Item choice : 1
pasting...
pasting complete
Enter your Menu choice
File : 0
Edit : 1
Exit : 2
2
Menu File destroyed
Menu_Item : Open destroyed
Menu_Item : Close destroyed
Menu Edit destroyed
Menu_Item : Copy destroyed
Menu_Item : Paste destroyed
Menu_Item : Cut destroyed
Document : hello is closed
Application : document close() detected
Application exiting...
Main Ends

So, what’s happening? Let’s analyze step by step.

  1. Application greeted the user with App name and asked to enter the file name to open for editing.
  2. User entered the file name ‘hello’
  3. Application gives user a choice to select a menu.  ‘0’ for File menu and ‘1’ for Edit menu
  4. User select File menu
  5. Application gives user another choice to select a menu item. ‘0’ for open, ‘1’ for close
  6. User selects open. Application opens the file ‘hello’ for editing
  7. Application gives user another choice to select the menu again.
  8. User selects Edit menu this time
  9. Application gives user a choice to select a menu item from Edit menu. ‘0’ for copy, ‘1’ for paste, ‘2’ for cut
  10. User selects copy, application executes copy.
  11. Application gives user another choice to select a menu again, for which user selects Edit menu and in turn select paste operation from Edit menu.
  12. Application executes paste
  13. Application gives user a choice to select a menu.
  14. This time user selects Exit option ‘2’.
  15. Application runs the clean up code. Clean up code just calls the reset() member function of unique_ptr.
  16. This in turn calls the destructors of each of the objects and elegantly deletes and releases back the memory
  17. In course of object deletion, destructor of Document class is also called which results in closing the file itself.

This is how you have to implement Command Design Pattern in modern C++.

Enjoyed the chapter? Let me know in the comments below. Thanks 🙂

0