The Memento pattern involves three main components:
The Originator is the object whose state needs to be saved and restored. It creates a memento object to store its current state and can also restore its state from a memento.
The Memento is an object that stores the internal state of the Originator. It provides methods to access the saved state, but only the Originator can use these methods to restore its state.
The Caretaker is responsible for managing the mementos. It requests the Originator to create a memento when needed and stores the memento. It can also request the Originator to restore its state from a memento.
Here is a simple UML - like representation of the relationships between these components:
+-----------------+ +-----------------+ +-----------------+
| Originator | | Memento | | Caretaker |
+-----------------+ +-----------------+ +-----------------+
| - state | | - state | | - mementos |
| + createMemento()|------>| | | + saveMemento() |
| + restoreMemento()|<-----| | | + getMemento() |
+-----------------+ +-----------------+ +-----------------+
Let’s implement a simple example of the Memento pattern in Java to demonstrate its usage.
// Memento class
class Memento {
private final String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// Originator class
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
}
// Caretaker class
import java.util.ArrayList;
import java.util.List;
class Caretaker {
private final List<Memento> mementos = new ArrayList<>();
public void saveMemento(Memento memento) {
mementos.add(memento);
}
public Memento getMemento(int index) {
return mementos.get(index);
}
}
public class MementoPatternExample {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// Set the initial state
originator.setState("State 1");
// Save the state
caretaker.saveMemento(originator.createMemento());
// Change the state
originator.setState("State 2");
// Save the new state
caretaker.saveMemento(originator.createMemento());
// Restore the first state
originator.restoreMemento(caretaker.getMemento(0));
System.out.println("Restored state: " + originator.getState());
}
}
In this example, the Originator
object can create and restore its state using the Memento
object, and the Caretaker
manages the storage of these mementos.
One of the most common use - cases for the Memento pattern is implementing an undo/redo feature in an application. We can extend the previous example to support multiple undo and redo operations.
import java.util.Stack;
// Extended Caretaker class for undo/redo
class ExtendedCaretaker {
private final Stack<Memento> undoStack = new Stack<>();
private final Stack<Memento> redoStack = new Stack<>();
public void saveMemento(Memento memento) {
undoStack.push(memento);
redoStack.clear();
}
public Memento undo() {
if (!undoStack.isEmpty()) {
Memento memento = undoStack.pop();
redoStack.push(memento);
return undoStack.isEmpty() ? null : undoStack.peek();
}
return null;
}
public Memento redo() {
if (!redoStack.isEmpty()) {
Memento memento = redoStack.pop();
undoStack.push(memento);
return memento;
}
return null;
}
}
The Memento pattern can also be used to save and load the state of an entire application. For example, in a game, we can save the player’s position, inventory, and other relevant data in a memento and load it later when the game is resumed.
Only save the necessary state in the memento to reduce memory usage. Storing unnecessary data in the memento can lead to performance issues, especially if there are a large number of mementos.
If you need to save the mementos to disk or send them over the network, use Java’s serialization mechanism. Make sure that the classes involved in the Memento pattern implement the Serializable
interface.
import java.io.Serializable;
// Serializable Memento class
class SerializableMemento implements Serializable {
private final String state;
public SerializableMemento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
Since the Memento pattern can add complexity to the codebase, it is important to provide clear documentation for the classes and methods involved. This will make the code more maintainable and easier for other developers to understand.
The Memento pattern is a powerful design pattern that allows us to save and restore the state of an object without violating encapsulation. It is particularly useful in scenarios where we need to implement features like undo/redo or save/load application state. By understanding the fundamental concepts, usage methods, common practices, and best practices of the Memento pattern in Java, developers can effectively leverage this pattern in their projects.
This blog provides a comprehensive overview of the Memento pattern in Java, and we hope it helps you understand and use this pattern effectively in your Java projects.