Implementing Undo-Redo Functionality with Memento Design Pattern
- Published on
Introduction
In software development, it is often necessary to implement undo-redo functionality, allowing users to revert back and forth between different states of an application. One way to achieve this is by using the Memento design pattern. The Memento pattern is a behavioral design pattern that enables us to save and restore the state of an object without violating encapsulation.
In this article, we will explore how to implement undo-redo functionality using the Memento design pattern in Java. We will discuss the structure and purpose of the Memento pattern, and then provide a step-by-step guide on how to implement it.
The Memento Design Pattern
The Memento design pattern is based on three main components:
-
Originator: This is the object whose state we want to save and restore. It has a method to create a Memento object representing its current state, as well as a method to restore its state from a Memento object.
-
Memento: This is an object that stores the state of the Originator. It provides methods for getting and setting the state.
-
Caretaker: This is the object that holds and manages the Memento objects. It is responsible for storing and retrieving Memento objects, and it provides methods for undoing and redoing operations.
Steps to Implement Undo-Redo Functionality
Now let's walk through the steps to implement undo-redo functionality using the Memento design pattern.
Step 1: Create the Originator class
First, we need to create the Originator class, which is the object we want to save and restore its state. This class should have a method to create a Memento object representing its current state, as well as a method to restore its state from a Memento object.
Here's an example implementation of the Originator class:
public class TextEditor {
private String text;
public TextEditor() {
this.text = "";
}
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
public TextEditorMemento createMemento() {
return new TextEditorMemento(text);
}
public void restoreMemento(TextEditorMemento memento) {
this.text = memento.getText();
}
}
In this example, we have a TextEditor
class that represents the object whose state we want to save and restore. It has a setText
method to set the text, a getText
method to get the text, a createMemento
method to create a Memento object, and a restoreMemento
method to restore the state from a Memento object.
Step 2: Create the Memento class
Next, we need to create the Memento class, which stores the state of the Originator. This class should have methods for getting and setting the state.
Here's an example implementation of the Memento class:
public class TextEditorMemento {
private final String text;
public TextEditorMemento(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
In this example, we have a TextEditorMemento
class that represents the Memento object. It has a getText
method to get the text stored in the Memento.
Step 3: Create the Caretaker class
Next, we need to create the Caretaker class, which holds and manages the Memento objects. This class is responsible for storing and retrieving Memento objects, and it provides methods for undoing and redoing operations.
Here's an example implementation of the Caretaker class:
import java.util.Stack;
public class Caretaker {
private final Stack<TextEditorMemento> mementos;
private int currentIndex;
public Caretaker() {
this.mementos = new Stack<>();
this.currentIndex = -1;
}
public void addMemento(TextEditorMemento memento) {
while (mementos.size() > currentIndex + 1) {
mementos.pop();
}
mementos.push(memento);
currentIndex++;
}
public TextEditorMemento undo() {
if (currentIndex > 0) {
currentIndex--;
}
return mementos.get(currentIndex);
}
public TextEditorMemento redo() {
if (currentIndex < mementos.size() - 1) {
currentIndex++;
}
return mementos.get(currentIndex);
}
}
In this example, we have a Caretaker
class that holds and manages the Memento objects. It has a Stack
to store the Memento objects, an addMemento
method to add a Memento object to the stack, an undo
method to undo the last operation, and a redo
method to redo the last operation.
Step 4: Testing the implementation
Finally, we can test our implementation by creating an instance of the TextEditor
class, as well as an instance of the Caretaker
class. We can then perform some operations on the TextEditor
, and use the Caretaker
to store and retrieve Memento objects to implement undo-redo functionality.
Here's an example test code:
public class Main {
public static void main(String[] args) {
TextEditor textEditor = new TextEditor();
Caretaker caretaker = new Caretaker();
// Perform some operations
textEditor.setText("Hello");
caretaker.addMemento(textEditor.createMemento());
textEditor.setText("Hello World");
caretaker.addMemento(textEditor.createMemento());
// Undo the last operation
textEditor.restoreMemento(caretaker.undo());
System.out.println(textEditor.getText()); // Output: Hello
// Redo the last operation
textEditor.restoreMemento(caretaker.redo());
System.out.println(textEditor.getText()); // Output: Hello World
}
}
In this example, we create a TextEditor
object and a Caretaker
object. We then set the text in the TextEditor
to "Hello" and store the Memento using the Caretaker
. We perform another operation by setting the text to "Hello World" and store another Memento. Finally, we undo the last operation and redo it to demonstrate the undo-redo functionality.
Conclusion
Implementing undo-redo functionality is a common requirement in many applications. By using the Memento design pattern, we can easily save and restore the state of an object without violating encapsulation. In this article, we discussed the structure and purpose of the Memento pattern, and provided a step-by-step guide on how to implement undo-redo functionality using the Memento pattern in Java.
Checkout our other articles