Creating An Undo Mechanism | The Memento Design Pattern
Today we'll be learning about the Memento Design Pattern which is used to create a rollback/undo feature in many projects. This is a part of our Weekly Meetups at Invide's Discord Server. Click here to join our Discord & participate in such meetups & events.
What is an undo/rollback mechanism?
Undo is a mechanism that allows the user to reverse any change done in any project. At the simple click of Ctrl + Z (CMD + Z for Mac) on a keyboard anyone can reverse or return to original state of their project/work.
What makes undo so special?
In applications like word processors, graphic design software, video editors, the undo feature is priceless. It allows users to save time re-editing their changes. And this feature adds the element of history saving & management to the application as well.
Consider the notepad editor, or vim the ubiquitous text editor. It's simple use case it to note things down in a document. It can save the text as a string & then it can return that whenever you open the file. Adding an undo feature needs special attention. It adds something called state saving (history) or checkpoints & rollback. When you press the undo button, you have to first detect the changes created from previous state and then dump them to return to the previous state.
However simpler it may sound, but current generation applications need to have multiple undo operations across multiple files. And sometimes these files are very large in size (consider undoing on a video editor or graphics processor like Photoshop while working with 4K quality).
The Memento Design Pattern
Just to address the challenges faced by above applications, the memento design pattern is used. It's a behavior design pattern first introduced in the book called "Design Patterns, Elements of Reusable Object Oriented Programming" by four authors famous known as Gang Of Four.
Intent behind Memento
Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later. Simply put, add checkpoints to rollback without breaking an object's encapsulation (the information binding mechanism in Object Orient Programming to secure data).
A memento is an object which stores a snapshot (or checkpoint) of another object which is generally noted as memento's originator.
Let's visualize the memento pattern's working using a class diagram and with the help of a text editor example.
Assume that you're working on a text editor that has the undo feature. Now we need a class named TextEditor, Memento, and History. All of these classes combined will have their own responsibilities.
TextEditor Class (aka Originator Class) will be responsible for having functions & features related to text editor. And it'll have a dependency relationship with Memento class for utilizing it's features to call the save/restore functions. It sets & gets mementos with the help of History class, and also creates new mementos. That's why it's the originator, the class whose states will be saved.
Memento Class will be responsible for storing the state of the editor at a given time. It also contains the mutable fields which are to be saved. The memento is the basic object stored in different states. For e.g. body text, title, subtitle, etc. Memento class have a composite relationship with the History Class to store the checkpoints, and it manages when to save & retrieve.
History Class contains all the check points with the help of Memento Class, and when needed adds or retrieves the checkpoints. It basically has a list that contains all the versions of memento.
Here's the UML diagram for it, that visualizes the above class relations.
With the above example we can understand how memento works and allows us to use and implement the undo feature flawlessly. However, if we were not to use this design pattern, we would rather have used a list of saved states directly in our text-editor class.
This would lead to a very complex design of the TextEditor class. The same class would be responsible for features & functionality of a text editor. Saving and managing checkpoints. And storing those checkpoints in memory. Imagine a class having three to four different roles. This would kill the purpose of creating a reusable class, and also violates the single responsibility principle (the S in SOLID Principles).
There is a problem with encapsulation violation in this design. Take the design, and see that an object's data (the originator) can now be stored & used by a different class's object (History).
This is can be solved by limiting how History access data from Originator or TextEditor. The Memento class is used to store the objects of TextEditor and they're not accessible to any other class apart from which it created. The History class is only storing the list of saved content's metadata like time, size etc. and therefore doesn't have any real data of the TextEditor class. It just adds memento objects into an array or list.
And this History class is technically called caretaker in the original GoF book. The TextEditor is called originator.
You can compare this image to the one above to find the similarities between the two. A stack or list (or any apt data structure) grows inside the caretaker class the more we keep adding the saved content from memento class.
The moment we press the undo button, the caretaker gets the most recent saved content's metadata, i.e., time-stamp when the metadata was created. This is sent to the originator class. Which then asks the memento to send the data according to the timestamp, and therefore allowing the originator to roll back to the previous state.
This is the working of memento design pattern. Future applications of this patterns would include to create a redo feature as well. Pressing that would allow the state to return to the future(now deleted) state.
Pros and Cons
- It allows user to implement the rollback function within any application flawlessly.
- User can modify the memento code as required to fit any application from text editors, UI tools to video editors.
- The size of saved object and growing mementos saved in the caretaker class is a problem. It can end up using high amount of memory.
- A universal or per application undo limit is required per application, per memory to store & restore data to prevent overconsumption of resources.
This memento design pattern is part of the Behavior Design Pattern class, and sometimes is also known as Token.
If you liked this article then please subscribe to our newsletter. Where we send in the best articles on technology (like this one), 100% remote developer jobs 🌎, developer productivity hacks and insights from the Invide Remote Developers Community.
If you would like to know more about these technologies, then join in our Discord Server. 😊