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

2 thoughts on “Chapter 13 : Series On Design Patterns – Command Pattern

Leave a Reply