Design Patterns: Memento
Memento captures an object's internal state into a snapshot. External caretakers can store and restore these snapshots without knowing the object's private implementation details.
Intent
Memento packages state into snapshot objects. External caretakers hold the history without needing to know what's inside.
The difference from Command: Command records actions and how to reverse them; Memento records state snapshots. Both are often used together in text editors.
Structure
- Originator: creates mementos and restores from them
- Memento: an immutable snapshot of a specific point in time
- Caretaker: holds the memento history without modifying it
Practical Example: Text Editor Undo History
// Memento
class EditorMemento {
constructor(
private readonly content: string,
private readonly cursorPosition: number,
private readonly timestamp: Date = new Date(),
) {}
getContent(): string { return this.content; }
getCursorPosition(): number { return this.cursorPosition; }
getTimestamp(): Date { return this.timestamp; }
}
// Originator
class TextEditor {
private content = '';
private cursorPosition = 0;
type(text: string): void {
this.content =
this.content.slice(0, this.cursorPosition) +
text +
this.content.slice(this.cursorPosition);
this.cursorPosition += text.length;
}
moveCursor(position: number): void {
this.cursorPosition = Math.max(0, Math.min(position, this.content.length));
}
getContent(): string { return this.content; }
getCursor(): number { return this.cursorPosition; }
save(): EditorMemento {
return new EditorMemento(this.content, this.cursorPosition);
}
restore(memento: EditorMemento): void {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
}
// Caretaker
class EditorHistory {
private mementos: EditorMemento[] = [];
save(memento: EditorMemento): void {
this.mementos.push(memento);
}
undo(): EditorMemento | undefined {
return this.mementos.pop();
}
getCount(): number { return this.mementos.length; }
}
const editor = new TextEditor();
const history = new EditorHistory();
history.save(editor.save());
editor.type('Hello');
history.save(editor.save());
editor.type(', World');
history.save(editor.save());
editor.type('!');
console.log(editor.getContent()); // 'Hello, World!'
editor.restore(history.undo()!);
console.log(editor.getContent()); // 'Hello, World'
editor.restore(history.undo()!);
console.log(editor.getContent()); // 'Hello'When to Use
Good fits
- Undo/redo functionality or periodic snapshots
- Object internals shouldn't be exposed to external storage mechanisms
Memento vs. Command
Command records what happened and how to reverse it. Memento records the state at a point in time. Text editors often use both: Command tracks operations, Memento snapshots state before each change.
Summary
Memento's core idea: save and restore state without exposing internals.
Draft state, rollback points, and auto-save before navigating away are all natural fits for Memento.