Memento Pattern — Capture and Restore Object State
What You'll Learn
You will learn how the Memento pattern saves and restores object state while keeping the saved data inaccessible to other objects, preserving Encapsulation. You will see how the Caretaker manages mementos without inspecting them.
Why It Matters
Undo/redo, checkpointing, and rollback are essential features in editors, games, and transactional systems. The na"ive approach — exposing all internal fields for serialisation — breaks Encapsulation. Memento lets the originator control exactly what state is captured, keeping internals private. The History caretaker never accesses the memento's fields; it just stores and retrieves opaque state containers.
Real-World Use
Video games save checkpoints capturing player position, health, and inventory state. DodaTech's pipeline designer provides undo/redo for every canvas operation — move, resize, delete — using mementos that capture the complete graph topology. Each canvas operation saves a memento before executing, allowing unlimited undo with minimal memory when combined with incremental state diffs.
The Pattern
Originator creates and restores mementos. Memento stores the originator's state (opaque to other objects). Caretaker requests mementos from the originator and stores them without inspecting contents.
from copy import deepcopy
class EditorMemento:
def __init__(self, content: str, cursor_pos: int, selections: list):
self._content = content
self._cursor_pos = cursor_pos
self._selections = selections
class TextEditor:
def __init__(self):
self._content = ""
self._cursor_pos = 0
self._selections = []
def type(self, text: str):
self._content = self._content[:self._cursor_pos] + text + self._content[self._cursor_pos:]
self._cursor_pos += len(text)
def move_cursor(self, pos: int):
self._cursor_pos = max(0, min(pos, len(self._content)))
def select(self, start: int, end: int):
self._selections.append((start, end))
def save(self) -> EditorMemento:
return EditorMemento(
deepcopy(self._content),
self._cursor_pos,
deepcopy(self._selections)
)
def restore(self, memento: EditorMemento):
self._content = memento._content
self._cursor_pos = memento._cursor_pos
self._selections = memento._selections
def __str__(self) -> str:
return f"Content: '{self._content}' | Cursor: {self._cursor_pos} | Selections: {self._selections}"
class History:
def __init__(self):
self._snapshots: list[EditorMemento] = []
def backup(self, editor: TextEditor):
self._snapshots.append(editor.save())
def undo(self, editor: TextEditor):
if self._snapshots:
editor.restore(self._snapshots.pop())
editor = TextEditor()
history = History()
history.backup(editor)
editor.type("Hello")
print(editor)
history.backup(editor)
editor.type(" World")
print(editor)
history.undo(editor)
print(f"After undo: {editor}")
history.undo(editor)
print(f"After second undo: {editor}")
Content: 'Hello' | Cursor: 5 | Selections: []
Content: 'Hello World' | Cursor: 11 | Selections: []
After undo: Content: 'Hello' | Cursor: 5 | Selections: []
After second undo: Content: '' | Cursor: 0 | Selections: []
Structure
classDiagram
class Originator {
-state
+save(): Memento
+restore(m: Memento)
}
class Memento {
-state
}
class Caretaker {
-mementos: list
+add(m: Memento)
+get(index): Memento
}
Originator --> Memento : creates
Caretaker --> Memento : stores
Real-World Usage
- Git — each commit is a memento of the entire Repository state; checkout restores it.
- Photoshop / GIMP history panel — every tool operation saves a layer memento for undo.
- Java
Serializable— combined with Memento, serialized objects serve as persistent snapshots. - PostgreSQL
SAVEPOINT— database savepoints act as mementos within a Transaction.
Related Patterns
- Command often saves a memento before executing to support undo.
- Prototype clones objects; Memento captures state for restoration.
- Iterator can store a memento to checkpoint traversal progress.
- State objects can be stored in mementos to restore the full state machine.
Pros and Cons
| Pros | Cons |
|---|---|
| Preserves Encapsulation boundaries | Can consume significant memory for large states |
| Simplifies originator by externalising state management | Caretaker must track memento lifecycle |
| Supports undo, rollback, and checkpointing | Restoring state may be costly for complex objects |
| Mementos are immutable snapshots | Language without deep copy make implementation harder |
The code uses deepcopy to create the memento — essential because the _content string and _selections list are mutable. The History caretaker stores mementos in a stack and provides backup and undo operations. When undo is called, it pops the last memento and calls editor.restore() — it never reads the memento's fields itself.
Practice Questions
- Implement a memento that captures state incrementally (diffs) instead of full snapshots to reduce memory usage.
- How would you limit the number of mementos stored by the caretaker to prevent memory exhaustion?
- Compare Memento with Event Sourcing — when would you choose one over the other?
- What Serialization concerns arise when a memento contains file handles or network connections?
Challenge
Implement a memento that supports both save and save_if_changed — only creating a new memento if the originator's state has actually changed since the last save. This avoids unnecessary snapshots.
Real-World Task
Identify an operation in your application where users would benefit from undo/redo. Implement it using the Memento pattern and use DodaTech's state diff tool to verify that only the intended state is captured.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro