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.

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.


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 -

Memento PD structure
(This diagram has been taken from Gof)

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 -

Memento DP
Example of memento design pattern (Click on picture to see zoom view)


#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

Popular posts from this blog

Non-virtual interface idiom (NVI)

Architectural patterns => Mud to structure => layers.

Architectural style -> Adoptable system -> Reflection.