Memento design pattern.
Problem statement –
I am hoping that we all are very much familiar with text
Editors (i.e., notepad++, text pad, MS word, etc.). The most important things we
do there (apart from copy and pest things from one place to another place) undo and
redo.
Or for game lovers, many times we perform undo step to rollback our game in previous position. We wish our life could also give these two
important command – undo and redo, but unfortunately, it is not there :(
Now the question is, how do we achieve tremendously helping two
commands undo and redo?
Background –
So we have a requirement of storing an object. The question here is, what we have understood from OOAD designs; is do not assign any
responsibility to any class. If that responsibility is not related to the class's core behavior.
For
Instance doing undo and redo is not the core behavior of TextEditor class. So
we would implement a class who perform undo and redo for TextEditor class.
When we say store the object for undo and redo purpose, it means we have to store the object's internal state. If another class is storing my class's object. It means another class has the idea of my class's internal state, which will end up with encapsulation violation.
When we say store the object for undo and redo purpose, it means we have to store the object's internal state. If another class is storing my class's object. It means another class has the idea of my class's internal state, which will end up with encapsulation violation.
So the task here is, how we can store the object, for redo and undo purpose, without violating encapsulation? Answer is "memento design pattern."
Means we have to rely on other class for storing our class's object status at any point of time. This is a classic example of implicit trusting. If you don’t
understand implicit and explicit trusting please refer to this link.
One more important thing to notice, we should have a threshold
limit of doing undo and redo commands. For example, 30, means our software can memories only 30 different actions taken by the user and able to undo and redo it. Obviously, the user
will perform more than 30 operations, so will keep on deleting old once and have
last 30 activities in memory.
Thanks for reading it. To learn more about design patterns and basic design principles, please see my web page. You can also join me on FB or on G++. Please drop comments for any question related to this blog.
Memento design pattern –
When there is a requirement to save different state of
object for undo and redo purpose (or maybe other customize purpose where you
have to keep previous states of object) we can directly use memento design
pattern.
Structure -
Different components of UML structure -
- Originator - is a class. For which we are aiming to undo and redo operation. It means we have to store (take snapshot of) Originator object.
- Memento - is responsible for snapshotting Originator class's object. And no one can access the stored object. Apart from Originator. This is where we are taking care of encapsulation.
- Caretaker - is responsible for initiating the command and request Memento to snapshot Originator object. The caretaker himself is not allowed to modify the Originator object. It is just a facilitator so that the Originator class can create a snapshot.
C++ Example -
#include<iostream> #include<string> #include<vector> #include<stdio.h> using namespace std; class NameMemento; class Names { private: string sName; public: Names(string name): sName(name){} string GetName() { return sName; } void SetName(string name) { sName = name; } NameMemento* CreateMemento() const; void ReinstantiateMemento (NameMemento* memento); }; class NameMemento { private: Names mName; public: NameMemento(const Names& name): mName(name){ } Names snapshot() const { return mName; } }; NameMemento* Names::CreateMemento() const { return new NameMemento(*this); } void Names::ReinstantiateMemento (NameMemento* memento) { *this = memento->snapshot(); } class Caretaker { public: typedef void(Names::*Action)(string); Names* mName; Action pFunction; vector<Caretaker*> vFunctions; vector<NameMemento*> vNames; int iCurrCmd; public: Caretaker (Names*newReceiver, Action newAction): mName (newReceiver), pFunction (newAction), iCurrCmd(0) {} void execute(string name) { vNames.emplace_back(mName->CreateMemento()); vFunctions.emplace_back(this); (mName->*pFunction)(name); // here pFunction is pointing to Names::SetName iCurrCmd = vFunctions.size(); } void Undo() { if (vNames.empty()) { std::cout << "There are no undo and redo has taken at this point of time" << std::endl; return; } vNames.emplace_back(mName->CreateMemento()); // saves the last value vFunctions.emplace_back(this); vFunctions[iCurrCmd - 1]->mName->ReinstantiateMemento(vNames[iCurrCmd -1]); --iCurrCmd; } void Redo() { if (iCurrCmd == vNames.size()) { std::cout << "We don't have anything to redo at this moment" << std::endl; return ; } Caretaker* caretakerRedo = vFunctions[iCurrCmd + 1]; if(caretakerRedo == NULL) { cout<<"We don't have anything to redo now, please perform some other operation"<<endl; return; } vFunctions[iCurrCmd + 1]->mName->ReinstantiateMemento(vNames[iCurrCmd + 1]); iCurrCmd++; } }; int main() { string name; int choice = 0; cout<<"Please enter your name -- "; cin>>name; Names*pName = new Names(name); Caretaker* caretaker = new Caretaker(pName, &Names::SetName); do { cout<<endl<<" Please select options"<<endl; cout<<"1 :Show the entered name"<<endl; cout<<"2 :Enter some other name"<<endl; cout<<"3 :Undo"<<endl; cout<<"4 :Redo"<<endl; cout<<"5 :Exit"<<endl; cout<<"......................................."; cin>>choice; fflush(stdin); switch(choice) { case 1: cout<<"you have entered - "<< pName->GetName()<<endl; break; case 2: cout<<"Enter another name - "; cin>>name; caretaker->execute(name); cout<<endl; break; case 3: caretaker->Undo(); break; case 4: caretaker->Redo(); break; default : cout<<"You have entered wrong choice, please select the menu once again"; cout<<endl; continue; } } while(choice != 5); return 0; }
Thanks for reading it. To learn more about design patterns and basic design principles, please see my web page. You can also join me on FB or on G++. Please drop comments for any question related to this blog.
Comments
Post a Comment