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:
The very interesting part here is the creation of a Promise object, which will be returned at method completion.
A Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.
A function that is passed with the arguments resolve and reject to the Promise contructor is called executor. The executor function is executed immediately by the Promise implementation, passing resolve and reject functions (the executor is called before the Promise constructor even returns the created object). The resolve and reject functions, when called, resolve or reject the promise, respectively.
In the invokeJsonp method, the executor creates a static method named danJsonpCallback inside DanJsonp class, by using Object.assign function.
This static method (DanJsonp.danJsonpCallback) will be invoked at the end of API execution, and only then it will “resolve” the promise, so to make the JSON data available to client.
In order to do that, a query parameter finalAddress += separator + "callback=DanJsonp.danJsonpCallback"; is concatenated to the final uri, and the script element is appended to the document body.