Design Patterns · Behavioral Patterns
Memento
Let a state owner create and restore its own snapshots. History stores the snapshots without learning the private representation.
This lesson follows Mediator and extends the undo example from Command.
Memento saves and restores an object's state while keeping that state private from history code. The owner defines the snapshot. A caretaker decides when to store or restore it. The cost is retaining state and managing snapshot lifetime.
Keep snapshot knowledge beside the state
An editor owns text and cursor position. If History reads those fields directly, every representation change requires an edit in History. Instead, the editor creates an opaque snapshot. History can hold it and show its label without interpreting its contents.
type Snapshot = Readonly<{ label: string }>;
class Editor {
#text = "draft";
#cursor = 0;
#states = new WeakMap<Snapshot, { text: string; cursor: number }>();
replace(text: string) { this.#text = text; this.#cursor = text.length; }
read() { return this.#text; }
save(label: string): Snapshot {
const token = Object.freeze({ label });
this.#states.set(token, { text: this.#text, cursor: this.#cursor });
return token;
}
restore(token: Snapshot): void {
const state = this.#states.get(token);
if (!state) throw new Error("Snapshot belongs to another editor");
this.#text = state.text;
this.#cursor = state.cursor;
}
}
class History {
private snapshots: Snapshot[] = [];
constructor(private editor: Editor) {}
checkpoint(label: string) {
this.snapshots.push(this.editor.save(label));
}
undo() {
const snapshot = this.snapshots.pop();
if (snapshot) this.editor.restore(snapshot);
}
}
const editor = new Editor();
const history = new History(editor);
history.checkpoint("Before rename");
editor.replace("revised");
history.undo();
editor.read(); // draft
The token is the caretaker's restricted view of the memento. The editor privately associates it with saved state. JavaScript private fields prevent History from reading that map. A snapshot from another editor is rejected. This implementation is in memory. The tokens cannot be serialized to recover the map after restart.
Restore all state that defines the promised result
Try it
Choose the snapshot boundary
before: text = draft, cursor = 0
edit: text = revised, cursor = 7
restore text only: cursor still 7
The visible text returns, but the old cursor does not. This is incomplete if undo promises to restore editing position.
restore text = draft
restore cursor = 0
editor returns to the captured state
Step through
Checkpoint before a change
Capture
The originator copies the state required for restoration. Copy mutable nested values rather than retaining live aliases.
editor.save('Before rename') → token
Retain
The caretaker stores tokens in order. It needs a history limit and an owner that releases abandoned entries.
history stack: [token]
Restore
The caretaker returns the token to the editor. The editor validates ownership and restores its private fields.
Map the roles
The book describes three ways to build these roles. They differ in how strictly the caretaker is kept away from the saved state:
| Role | In this example | Job |
|---|---|---|
| Originator | Editor | Produces snapshots of its own state and restores its state from them when needed |
| Memento | the frozen Snapshot token plus the state the editor keeps privately for it | A value object that acts as a snapshot of the originator's state. Usually immutable, with data passed once through the constructor. |
| Caretaker | History | Knows when and why to capture the originator's state, and when to restore it. Keeps a stack of mementos and passes the top one back to the originator to undo. |
| Implementation | How it protects the state | Trade-off |
|---|---|---|
| Nested class | The memento class is nested inside the originator, so only the originator reads its fields. The caretaker can only store it. | Needs a language with nested classes, such as Java, C#, or C++ |
| Intermediate interface | The caretaker sees the memento only through an interface that declares metadata methods, such as a label or a timestamp. The originator uses the memento class directly. | The memento's fields must be public, so the rule is enforced by convention |
| Stricter encapsulation | Each memento is linked to the originator that created it and holds the restore method itself. The caretaker never touches state and does not depend on the originator. | Only works when the memento can set the originator's state, through nesting or enough setters |
Check yourself
This lesson's Editor keeps saved state in a private WeakMap keyed by a frozen token. Which of the book's variants does it most resemble?
Not quite. TypeScript has no nested classes. Private fields play that role here.
Correct. History reads only the label. Private fields stop it from reading the state.
Reach for it when you need snapshots without breaking encapsulation
- you want to snapshot an object's state so you can restore it later. Memento copies the full state, including private fields, and stores it apart from the object. Undo is the famous case, but transactions that roll back after an error need it too.
- reading the object's fields, getters, or setters directly would break its encapsulation. The object makes its own snapshot. No other object can read the snapshot, which keeps the original object's data safe.
Check yourself
A batch job edits an in-memory order. If any step throws, the order must return to how it was. Which Memento use is this?
Not quite. No user asks for undo here. The rollback happens automatically on failure.
Correct. Capture a memento before the job and restore it in the error path.
Implement it in eight steps
Refactor existing code toward the pattern in this order:
- Decide which class plays the originator. Know whether the program uses one central originator or several smaller ones.
- Create the memento class. Declare one field for each originator field you must save.
- Make the memento immutable. It receives its data once, through the constructor, and has no setters.
- If your language supports nested classes, nest the memento inside the originator. If not, extract an empty interface from the memento and make every other object use it. You can add metadata methods to the interface, but nothing that exposes the originator's state.
- Add a method to the originator that creates mementos. The originator passes its state to the memento through constructor arguments. The return type is the interface from the previous step, if you extracted one.
- Add a method to the originator that restores its state from a memento. If you extracted an interface, accept it and cast it back to the memento class, because the originator needs full access.
- Make the caretaker, whether a command, a history, or something else, know when to request new mementos, how to store them, and when to restore the originator with a particular one.
- Optionally, move the link between caretakers and originators into the memento: each memento is tied to the originator that made it, and the restore method moves into the memento. That only works when the memento is nested in the originator or the originator has enough setters.
Check yourself
Why must the memento be immutable?
Correct. A snapshot that can change is no longer a reliable record.
Not quite. Mementos belong to the originator that created them.
Name what it costs
| You gain | You pay |
|---|---|
| Snapshots of an object's state without violating its encapsulation | Frequent mementos consume a lot of memory |
| The originator's code stays simple, because the caretaker keeps the history | Caretakers must track the originator's life cycle to destroy obsolete mementos |
| The state representation can change inside the owner without touching the caretaker | Dynamic languages such as PHP, Python, and JavaScript cannot fully guarantee a memento's state stays untouched |
| Labeled snapshots can be listed for the user, like a visible history | Persistent snapshots need versioning and migration rules |
Local restoration does not reverse external effects
Restoring an editor snapshot does not undo a message already sent or a write in another service. State the rollback boundary. A shared editor also needs a concurrency policy so restoring an old snapshot does not erase another writer's change.
Check yourself
Saving a full 5-mebibyte document before 200 edits has what basic cost before overhead or compression?
Not quite. Independent full snapshots retain separate state values.
Correct. Full-copy history multiplies state size by retained versions. Limit history or choose a different representation.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Command | Use them together for undo. Commands perform operations on a target object, and mementos save that object's state just before each command runs. |
| Iterator | Use a memento to capture an iterator's current position and roll back to it if needed. |
| Prototype | Sometimes a simpler substitute: clone the object when its state is simple and holds no links to external resources, or links that are easy to re-establish. Cloning alone does not define the history or the restore contract. |
Check yourself
An undo stack stores a command plus the editor state from just before it ran. Which two patterns are working together?
Correct. The command performs the change, and the memento records the state to restore.
Not quite. Neither pattern stores history or reverses changes.
Retrieve and apply
Check yourself
History stores a reference to the editor's mutable selection array. The editor then changes that array. Is the old selection preserved?
Correct. The saved state changed with the current state. Copy the needed mutable values when capturing the snapshot.
Not quite. A reference preserves access to the same object, not its old values.
List the state required to undo one operation, including the state a naive copy would miss. Continue to Observer to notify a changing set of subscribers.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Memento, printed pages 300–314. Explanations, examples, and exercises are adapted for this course.