The Memento Pattern: Your Code’s Personal Time Machine
What is the Memento Design Pattern?
The memento design pattern allows you to capture and restore the internal state of an object without exposing its implementation details. It allows you to store the object’s current state and roll it back to a previous state when necessary.
A real-world use case of the memento design pattern is the undo feature of the Text editor.
Let’s check how it works. Without using any fancy terms, I will try to explain it simply.
Example of Memento Design Pattern:
Let’s design a simple text editor. We will create a class named Editor
with a single private field named content
to store the text. To access and modify the content we will need two additional methods, a getter and a setter.
Imagine you’re typing content in the editor. This content gets saved in the content
field. If you make a mistake or want to go back, how can you undo your writing?
One of the easiest solutions is to introduce another field variable, prevContent
. We’ll also need setters and getters for it.
Whenever we edit the content
in the Editor, it first saves a copy of the existing content to the variable prevContent
. Then, it updates the editor’s content
with the new value. This allows us to undo the previous edit by retrieving the content from prevContent
and restoring it to the Editor
. In this way, we can easily revert to the previous state of the content.
This is a good start. But it allows for a single undo. In real-world text editors you typically want the ability to undo multiple changes.
To solve this, we can change prevContent
, from string to list of prevContents
Let’s check, how it will look in that case:
It is a much better solution! Now we can store multiple versions of the content in a list, allowing us to undo changes as needed.
But with this, another problem arises. While it allows undo functionality to multiple states, it requires a separate variable for each type of content change. For example, if we introduce a image
field to store images, we’d need another list to track image history. This can become cumbersome as we add more features.
With every additional introduction of a field variable, each one requires a new variable to track its history for undo. This can quickly lead to a cluttered class with many fields, making it difficult to manage and maintain.
So how we can solve this?
Instead of adding multiple fields in this class, let’s create a dedicated class EditorState
. This class will be responsible for storing the entire state of the Editor
at a given time. This approach keeps the Editor
class cleaner and easier to manage as new features are added.
For simplicity let's consider our Editor
class has only one field, that is content
. With that, our EditorState
class will be as follows:
Now in the Editor
class, we need to store the list of editor states. So we will have a composition relationship between these two classes.
Now we can add multiple fields without polluting the Editor
class with too many variables. Whenever there is a change we just set a new EditorState
and whenever we need we can restore it from that.
But this violates one important principle of Object Oriented Programming. That is the Single Responsibility Principle.
In simple words, the single responsibility principle means one class should have only one responsibility.
Imagine a restaurant where a single person is responsible for taking orders, cooking the food, serving it to the customers, collecting bills, and managing accounting.
I can't imagine it other than a complete disaster. Soon there will be chaos, the restaurant needs to be shut down.
The same thing applies to Object Oriented Programming. If a single class is responsible for doing so many tasks, then soon it will become unmaintainable.
In our case, our editor class has two responsibilities.
- Doing his own work. That is holding the content.
- Managing the state of the content.
To get rid of this situation let's introduce a new class, called History
.
This History
class will have a field called state
. This state
field is a list that holds zero or more EditorState
objects. Each EditorState
object captures the complete state of the editor at a specific point in time, including content, and other potential features like image or formatting.
The History
class will also have two important methods:
push(state)
: This method adds a newEditorState
object to thestate
list.pop
: This method retrieves the most recentEditorState
object from thestate
list, allowing us to restore the editor to its previous state.
At this point History
will have a composition relationship with the EditorState
, and the Editor
class itself no longer maintains individual states. To interact with History
class, we need to add two new methods to the Editor
class:
createState()
: This method will capture the current state of the editor and create a newEditorState
object. It will then pass this object to theHistory
class(usingpush
the method we discussed earlier) to store the state.restoreState()
: This method will retrieve the most recentEditorState
object from theHistory
(usingpop
method) and use it to restore theEditor
's content and other properties to their previous state.
Now the class diagram looks like this:
Once the Editor
is restored, the state object is removed from the list.
This functionality creates a dependency between the Editor
class and the EditorState
class since the Editor
relies on the state object to restore its state.
This is how these classes collaborate.
Congratulations! We have just used the memento pattern to implement the undo mechanism in our classes.
Now let’s take a moment to understand how our custom class names align with the broader design pattern terminology used in many resources and books.
In the Memento Design Pattern, there are three key components:
Originator: This is the object whose state needs to be tracked for undo changes. In our case Editor
class perfectly fits this role.
Memento: This is a separate object that stores originators' states at a specific point in time. Our EditorState
class directly translates to a memento. It holds a snapshot of the editor’s state at a particular moment.
Caretaker: This class is responsible for managing the mementos. Our History
class aligns with this caretaker role. It manages the collection of editor states, adding them, retrieving them and providing them back to the editor for restoration.
Now let's check the coding example. Though the design pattern is language-independent, I am going to use Java programming language. You will also find the code in my GitHub repository.
First of all our Editor
it has only one field content
and setter and getter method for the content.
public class Editor {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
To manage the states of the Editor
at different points in time, we’ll introduce a new class called EditorState
.
public class EditorState {
private String content;
public EditorState(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
This EditorState
class is specifically designed to capture a snapshot of the editor's content at a particular moment in time. It serves as a memento, holding the essential information needed to restore the editor’s state for undo operation.
Here is the breakdown of the components:
- A Private Field: It has a single private field called
content
of typeString
. This field stores the actual content of the editor at the time theEditorState
object is created. - Constructor: The public constructor
EditorState(String content)
allows us to crate newEditorState
objects. It takes the current editor content as a string argument and assigns it to the privatecontent
field. - Getter Method: The public getter method
getContent()
provides a way for other parts of the program to access the stored content. It simply returns the value of thecontent
field allowing retrieval of the captured editor state.
This class keeps the editor’s state management separate from the editor itself, promoting clearer code and easier implementation of undo feature.
Now back to our Editor
class. To enable undo functionality we will add two new methods: crateState
and restore
public class Editor {
private String content;
public EditorState createState() {
return new EditorState(content);
}
public void restore(EditorState state) {
content = state.getContent();
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
Now the last pieces of the puzzle, the History
class:
import java.util.Stack;
public class History {
private Stack<EditorState> editorStates = new Stack<>();
public void push(EditorState state) {
editorStates.push(state);
}
public EditorState pop() {
return editorStates.pop();
}
}
This History
class is the last component we need for undo functionality. It utilizes a Stack
data structure to maintain a list of EditorState
objects. These EditorState
objects, as you recall represent snapshots of the editor content at different points in time.
Now let's combine all together and see the undo mechanism in action. I have created a main class that holds the main method.
public class Main {
public static void main(String[] args) {
Editor editor = new Editor();
History history = new History();
editor.setContent("a");
history.push(editor.createState());
editor.setContent("b");
history.push(editor.createState());
editor.setContent("c");
editor.restore(history.pop());
System.out.println(editor.getContent()); // prints b in the console
}
}
Here’s a step-by-step breakdown of how we use these classes together in the main method:
- Create the Editor and History: We begin by creating an
Editor
object to manage the content and aHistory
object to track changes. - Set Content, Save States: We set the initial content “a” and call
editor.createState()
to save a snapshot. This is pushed to theHistory
usinghistory.push()
. We repeated this for subsequent changes “b” and “c”. - Undo: To undo the last change (“c” to “b”), we simply call
history.pop()
. This retrieves the previous state and restores the editor's content.
Congratulations! You’ve successfully navigated the world of the Memento pattern and its implementation for undo/redo functionality.
Thank you for your patience in reading this explanation. I’ve strived to break down the concepts into clear and understandable steps.
If you’d like to explore further, consider delving into real-world applications of the Memento pattern beyond text editors. It’s a versatile design pattern used in various software development scenarios!
If you found this article helpful, please consider showing your support by following me and leaving a comment below.
For fellow freelancers, I invite you to explore my YouTube channel and connect with me on LinkedIn. I look forward to meeting you.
I specialize in Java and Spring Boot development as a freelance software developer. If you require assistance with your Spring Boot application, don’t hesitate to get in touch.
Thank you for your time. You can also find me on Fiverr and Upwork. Feel free to reach out there as well.