This design decouples the CListable abstraction from its implementation (in this case: CBook, CCdRom, CDvd), so that the two can vary independently: it complies to the Bridge Design Pattern.
Undo/Redo: Command and Memento patterns.
Command pattern is an Object behavioral pattern that decouples sender and receiver by encapsulating a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and above all support undo-able operations. It can also be thought as an object oriented equivalent of call-back method.
Without violating encapsulation the Memento pattern will capture and externalize an object’s internal state so that the object can be restored to this state later. The Originator (the object to be saved) creates a snap-shot of itself as a Memento object, and passes that reference to the Caretaker object. The Caretaker object keeps the Memento until such a time as the Originator may want to revert to a previous state as recorded in the Memento object.
In my implementation the originator is represented by CUndoableInventory. CUndoableInventory creates a snap-shot of itself as a CUndoableInventoryMemento object by invoking createMemento(), and can revert to a previous state by invoking reinstateMemento (CUndoableInventoryMemento* mem):
An instance of this class represents an undoable operation performed on the CUndoableInventory object: it keeps a pointer to an instance of the receiver (CUndoableInventory) and the method to be performed on the receiver as a function<void(const CUndoableInventory&)> (the std::function encapsulates both the method of class CUndoableInventory and the parameters to be passed to it).
Moreover, the CUndoableInventoryAction class is the Caretaker, because it keeps two static lists:
1) the list of actions performed;
2) the list of the inner states of the undoable inventory (CUndoableInventoryMemento object).
The undo/redo functions have been implemented as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void CUndoableInventoryAction::undo()
{
if(numCommands_ == 0) {
std::cout << "There is nothing to undo at this point." << std::endl;
CUndoableInventoryAction* myInventoryAction = new CUndoableInventoryAction(&undoableInventory_, method);
myInventoryAction->execute();
}
which receives a function<void(const CUndoableInventory&)> as parameter, which encapsulates the method of CUndoableInventory to be invoked and the parameters. This method creates an action (Command pattern) by creating an object of type CUndoableInventoryAction, and then invokes its execute method myInventoryAction->execute();.
In this way, any function willing to be undoable, will just have to wrap the CUndoableInventory method and the parameters, inside a std::function
For example, let’s have a look at inventoryAddListable(CListableFactory::listable_t listableType, const std::string& name):
In order to “bind” the QML undoable listView with the data model represented by CUndoableInventoryManager object, I set up a MVC architecture.
Model: CUndoableInventoryManager
View: QML undoable listView
Controller: an object of type CInventoryModel extending QAbstractListModel
The class CInventoryModel receives commands from Javascript functions used in QML undoable listView, and then it performs modifications on the data model and the listView itself. For example, let’s have a look at the addListable method: