Memento
Memento pattern: capture and restore object state without exposing internals. The shape behind undo / redo.
Design patterns — Memento
EXAMPLE
// ===== Roles =====
// Originator: the object whose state to capture
// Memento: opaque snapshot of state
// Caretaker: manages the history of mementos (undo stack)
// ===== Tiny example: text editor =====
class Editor {
private content = '';
type(text: string) { this.content += text; }
delete(n: number) { this.content = this.content.slice(0, -n); }
toString() { return this.content; }
save(): EditorMemento {
return new EditorMemento(this.content);
}
restore(m: EditorMemento) {
this.content = m.state;
}
}
class EditorMemento {
// Package-private state; only Editor uses it.
constructor(public readonly state: string) {}
}
class History {
private stack: EditorMemento[] = [];
push(m: EditorMemento) { this.stack.push(m); }
pop(): EditorMemento | undefined { return this.stack.pop(); }
}
// ===== Usage =====
const e = new Editor();
const h = new History();
h.push(e.save()); // snapshot empty
e.type('hello');
h.push(e.save());
e.type(' world');
console.log(e.toString()); // 'hello world'
e.restore(h.pop()!);
console.log(e.toString()); // 'hello'
e.restore(h.pop()!);
console.log(e.toString()); // ''
// ===== With deep state =====
class Document {
constructor(public title: string, public lines: string[]) {}
save(): DocMemento { return new DocMemento(this.title, [...this.lines]); }
restore(m: DocMemento) {
this.title = m.title;
this.lines = [...m.lines];
}
}
class DocMemento {
constructor(public readonly title: string, public readonly lines: string[]) {}
}
// Pattern: deep copy on save, deep copy on restore -> mementos cannot be mutated externally.
// ===== With incremental snapshots =====
// For big state, store DELTAS instead of full snapshots:
class IncrementalEditor {
private content = '';
private history: Array<() => void> = []; // undo ops
type(text: string) {
this.content += text;
const n = text.length;
this.history.push(() => this.content = this.content.slice(0, -n));
}
undo() {
const op = this.history.pop();
op?.();
}
}
// Trade memory for time on undo.
// ===== Real applications =====
// - Text / drawing editors (undo / redo)
// - Game state snapshots (save points, network rollback)
// - Database transactions (begin / rollback / commit)
// - Form drafts (auto-save / restore)
// ===== Patterns to internalise =====
// - Memento is OPAQUE to the caretaker (only the originator interprets it)
// - Deep-copy mutable state at save + restore
// - For huge state, store deltas instead of snapshots
// - Pair with Command pattern for richer undo / redo
// ===== Pitfalls =====
// - Memento exposing setters -> external code mutates it
// - Shallow copies that share refs with the live object
// - Unbounded history -> memory growth; cap with a ring buffer
// - Combining memento with side effects (network calls, IO) -> can't truly roll back
Why it matters
Memento snapshots and restores object state without exposing internals. Deep copy on save + restore; store deltas for huge state; cap the history. The pattern shows up in editors, games, transactions, and form drafts. Pair with Command pattern when undo logic needs more than a snapshot.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Capture and restore state without exposing internals.
class Editor {
constructor(text = '') { this.text = text; }
save() { return this.text; }
restore(memento) { this.text = memento; }
}
Try it Yourself »
Discussion
Loading…