Design PatternsBehavioral
Memento Pattern
Capture and restore an object's internal state without violating encapsulation — the basis of undo and snapshots.
Design Patterns
Intent
Memento lets an object (the originator) produce a snapshot of its own state — the memento — that some other object (the caretaker) can store and later hand back to restore that state. Crucially, the caretaker can’t read or modify the snapshot’s contents, so the originator’s encapsulation is preserved.
Structure
classDiagram
direction LR
class Editor {
-String text
-int cursor
+save() Snapshot
+restore(Snapshot)
}
class Snapshot {
<<record>>
-String text
-int cursor
}
class History {
-Deque~Snapshot~ stack
+push(Snapshot)
+pop() Snapshot
}
Editor ..> Snapshot : creates / restores
History o--> Snapshot : stores (opaque)
Example: undo history and checkpoints
import java.util.*;
class Editor { // originator
private String text = "";
private int cursor = 0;
private String font = "Inter";
// The memento: an immutable record nested in the originator.
// Its accessors are private to Editor's nesting scope in spirit —
// the caretaker treats it as an opaque token.
record Snapshot(String text, int cursor, String font, long takenAt) {}
void type(String s) { text = text.substring(0, cursor) + s + text.substring(cursor); cursor += s.length(); }
void moveCursor(int pos) { cursor = Math.max(0, Math.min(pos, text.length())); }
void setFont(String f) { font = f; }
Snapshot save() { return new Snapshot(text, cursor, font, System.nanoTime()); }
void restore(Snapshot s) { text = s.text(); cursor = s.cursor(); font = s.font(); }
public String toString() {
return "[" + font + "] " + text.substring(0, cursor) + "|" + text.substring(cursor);
}
}
class History { // caretaker
private final Deque<Editor.Snapshot> stack = new ArrayDeque<>();
private final int limit;
History(int limit) { this.limit = limit; }
void push(Editor.Snapshot s) {
stack.push(s);
if (stack.size() > limit) stack.removeLast(); // bounded memory
}
Optional<Editor.Snapshot> pop() { return Optional.ofNullable(stack.poll()); }
}
class Main {
public static void main(String[] args) {
var editor = new Editor();
var history = new History(10);
history.push(editor.save()); editor.type("Hello world");
history.push(editor.save()); editor.moveCursor(5); editor.type(",");
history.push(editor.save()); editor.setFont("JetBrains Mono"); editor.type(" cruel");
System.out.println("now: " + editor);
history.pop().ifPresent(editor::restore);
System.out.println("undo: " + editor);
history.pop().ifPresent(editor::restore);
System.out.println("undo: " + editor);
history.pop().ifPresent(editor::restore);
System.out.println("undo: " + editor);
}
}Output
Memento + Command
The two patterns combine naturally for generic undo: before a command executes, take a memento of the receiver; undo() restores it. This avoids writing a hand-crafted inverse for every operation, at the cost of memory.
final class SnapshotCommand implements Command {
private final Editor editor;
private final Consumer<Editor> action;
private Editor.Snapshot before;
public void execute() { before = editor.save(); action.accept(editor); }
public void undo() { editor.restore(before); }
}
Where you’ll see it
- Undo/redo in editors, drawing tools and IDEs
java.io.Serializable— serialising an object to bytes is an (unencapsulated) memento- Game save points; database savepoints (
Connection.setSavepoint()/rollback(savepoint)) - Spring Web Flow / Spring State Machine persisting state snapshots
- Event sourcing snapshots — periodically storing an aggregate’s state so you don’t replay every event
Trade-offs
| Pros | Cons |
|---|---|
| Undo without exposing internal state | Memory-hungry if snapshots are large or frequent |
| Originator stays simple — caretaker manages history | Caretaker must manage lifecycle (limits, eviction) |
| Snapshots can be persisted or sent over the wire | In Java, true opacity is hard to enforce |
Finished this lesson?
Mark it complete to track your progress through Design Patterns.