Low-level design3 min
Memento Pattern
Type: Behavioral Pattern
Relevance: Medium. Crucial for implementing complex Undo/Redo features or state snapshots.
The Memento pattern lets you save and restore the previous state of an object without revealing the details of its implementation (violating encapsulation).
Real-Life Analogy
Think of Saving a Video Game. When you reach a checkpoint, the game saves your level, health, weapons, and location into a "Save File" (The Memento). If your character dies, you load the save file. The save file itself is completely locked; you can't open it in a text editor and change your health to infinite. Only the game engine (The Originator) knows how to read and write to the save file.
The Problem
If you want to create an "Undo" feature in an app, you need to save the state of an object. The easiest way is to copy its fields. But what if most of the object's fields are private? If you expose all fields as public just to save them, you violate Encapsulation and tightly couple your code.
How to Implement Memento
Create a Memento class that holds a snapshot of the Originator's state. The Memento is completely locked down (no setters). Only the Originator can instantiate it and read its data. A Caretaker (like the UI or a history manager) can hold an array of Mementos, but it cannot peek inside them.
Example in Code
// 1. The Memento (The "Save File")
// Notice there are NO setters! It is immutable.
class EditorMemento {
constructor(
private content: string,
private cursorPosition: number
) {}
public getContent(): string { return this.content; }
public getCursorPosition(): number { return this.cursorPosition; }
}
// 2. The Originator (The actual object doing the work)
class TextEditor {
private content: string = "";
private cursorPosition: number = 0;
public type(words: string): void {
this.content += words;
this.cursorPosition += words.length;
console.log(`Current Text: "${this.content}"`);
}
// Creates a snapshot!
public save(): EditorMemento {
console.log("Editor: Saving state...");
return new EditorMemento(this.content, this.cursorPosition);
}
// Restores a snapshot!
public restore(memento: EditorMemento): void {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
console.log(`Editor: State restored to "${this.content}"`);
}
}
// 3. The Caretaker (Manages the history)
class HistoryManager {
private history: EditorMemento[] = [];
constructor(private editor: TextEditor) {}
public backup(): void {
this.history.push(this.editor.save());
}
public undo(): void {
if (!this.history.length) return;
const memento = this.history.pop();
if (memento) {
this.editor.restore(memento);
}
}
}
// Client Code
const editor = new TextEditor();
const history = new HistoryManager(editor);
editor.type("Hello");
history.backup(); // Save snapshot 1
editor.type(" World");
history.backup(); // Save snapshot 2
editor.type("! And Universe!"); // Made a mistake!
// Undo!
history.undo(); // Restores to "Hello World"
history.undo(); // Restores to "Hello"
Class Diagram
Why is it Important?
- Preserves Encapsulation: It doesn't expose the Originator's private state to the outside world.
- Simplifies the Originator: The Originator doesn't need to track its own history; that responsibility is pushed to the Caretaker.