State Preservation and Recovery Using the Memento Pattern
Consider a document editing application where users compose text but lack the ability to reverse accidental deletions. Without a mechansim to capture prior conditions, each modification permanently alters the working state.
public class Draft {
private StringBuilder manuscript = new StringBuilder();
public void insert(String segment) {
manuscript.append(segment);
}
public void backspace() {
int length = manuscript.length();
if (length > 0) {
manuscript.delete(length - 1, length);
}
}
public String content() {
return manuscript.toString();
}
}
In this implemantation, once a character is removed via backspace(), the previous state is irretrievably lost. The Memento pattern addresses this limitation by externalizing state storage without violating encapsulation.
The Memento behavioral pattern captures and externalizes an object's internal state so that the object can be restored to this state later. This pattern involves three distinct participants: the Originator, which possesses the state to preserve; the Memento, which acts as an immutable storage container for the Originator's state; and the Caretaker, which oversees the lifecycle of stored states without accessing their contents.
To implement undo functionality, the Originator must expose methods for state serialization and deserialization. The Memento remains opaque to all objects except the Originator that created it, ensuring data integrity.
public class Draft {
private StringBuilder manuscript = new StringBuilder();
public void insert(String segment) {
manuscript.append(segment);
}
public void backspace() {
int length = manuscript.length();
if (length > 0) {
manuscript.delete(length - 1, length);
}
}
public String content() {
return manuscript.toString();
}
public Checkpoint save() {
return new Checkpoint(manuscript.toString());
}
public void restore(Checkpoint checkpoint) {
manuscript = new StringBuilder(checkpoint.getState());
}
public static class Checkpoint {
private final String state;
private Checkpoint(String state) {
this.state = state;
}
private String getState() {
return state;
}
}
}
The Caretaker maintains a history of checkpoints while remaining ignorant of their internal structure. It functions solely as a repository, pushing states onto a stack during save operations and popping them to facilitate rollbacks.
public class VersionControl {
private java.util.Deque<Draft.Checkpoint> history = new java.util.ArrayDeque<>();
public void commit(Draft draft) {
history.push(draft.save());
}
public void rollback(Draft draft) {
if (!history.isEmpty()) {
draft.restore(history.pop());
}
}
}
Demonstrating state recovery:
public class Application {
public static void main(String[] args) {
Draft document = new Draft();
VersionControl versions = new VersionControl();
document.insert("Initial paragraph. ");
document.insert("Additional content.");
versions.commit(document);
document.backspace();
System.out.println(document.content()); // Output: Initial paragraph. Additional content
versions.rollback(document);
System.out.println(document.content()); // Output: Initial paragraph. Additional content.
}
}
This architecture proves valuable across numerous domains. In interactive graphics software, transformations such as rotation or scaling can be captured as discrete states, allowing artists to step backward through complex editinng sequences. Gaming applications utilize this approach for save-game mechanics, preserving player position, inventory, and quest progress without exposing internal game engine structures to persistence layers. Database transaction managers implement similar rollback capabilities, maintaining snapshots before query execution to ensure atomicity. Version control systems like Git fundamentally operate on these principles, storing repository states as objects that can be checked out to reconstruct prior project snapshots.