Memento Pattern
The Memento Pattern is a behavioral design pattern that lets you save and restore the previous state of an object without revealing the details of its implementation. It is the core mechanism behind "Undo" operations.
1. The Core Concept
The pattern delegates creating the state snapshots to the actual owner of that state, the Originator object. Hence, other objects don't need to peek into the private details of the Originator.
- Originator: The object that has an internal state. It can create a snapshot (Memento) of its current state and restore its state from a Memento.
- Memento: A value object that acts as a snapshot of the Originator's state. It is usually immutable and opaque to other objects.
- Caretaker: The object that keeps track of the Mementos (e.g., a history stack). It requests a save from the Originator and stores the Memento, but never modifies the Memento's contents.
2. Analogy: Video Game Save Points
Imagine playing a difficult RPG.
- Before a boss fight, you hit "Quick Save".
- The game creates a snapshot of your character's stats, inventory, and location (Memento).
- The game system stores this file (Caretaker).
- If you lose the fight, you load the save file, and the game restores your character to that exact state (Originator restores).
3. Example (TypeScript)
Let's implement a simple text editor with undo functionality.
// The Memento
class Memento {
private state: string;
constructor(state: string) {
this.state = state;
}
getState(): string {
return this.state;
}
}
// The Originator
class Editor {
private content: string = "";
type(words: string) {
this.content += " " + words;
}
getContent(): string {
return this.content;
}
save(): Memento {
return new Memento(this.content);
}
restore(memento: Memento) {
this.content = memento.getState();
}
}
// The Caretaker
class History {
private mementos: Memento[] = [];
private editor: Editor;
constructor(editor: Editor) {
this.editor = editor;
}
backup() {
this.mementos.push(this.editor.save());
}
undo() {
if (this.mementos.length === 0) return;
const memento = this.mementos.pop();
if (memento) {
this.editor.restore(memento);
}
}
}
// Usage
const editor = new Editor();
const history = new History(editor);
editor.type("This is the first sentence.");
history.backup(); // Save state
editor.type("This is the second.");
history.backup(); // Save state
editor.type("This is a mistake.");
console.log(editor.getContent());
// Output: This is the first sentence. This is the second. This is a mistake.
history.undo(); // Undo mistake
console.log(editor.getContent());
// Output: This is the first sentence. This is the second.
history.undo(); // Undo second sentence
console.log(editor.getContent());
// Output: This is the first sentence.
4. Pros and Cons
| Pros | Cons |
|---|---|
| Encapsulation: You can produce snapshots of the object's state without violating its encapsulation (private fields remain private). | Memory Usage: Saving the entire state frequently can consume a lot of RAM. |
| Simplification: The Originator code is cleaner because it doesn't need to manage the history of changes. | Lifecycle: The Caretaker needs to track the lifecycle of the Originator to avoid memory leaks or restoring to a deleted object. |
programming/design-patterns programming/object-oriented-programming programming/command-pattern