Skip to main content

Memento Pattern

Capture and restore object state without violating encapsulation

Pattern Overview

💾 Memento Pattern

The Memento Pattern captures and externalizes an object's internal state without violating encapsulation, allowing the object to be restored to this state later.

Core Concepts

🔹 Originator - Object whose state needs to be saved and restored
🔹 Memento - Stores the internal state of the originator
🔹 Caretaker - Manages mementos but never examines their contents
🔹 Encapsulation - Memento protects originator's state from external access

Real-World Applications

Text Editors - Undo/redo functionality with document state snapshots Games - Save/load game state at checkpoints or user request Configuration Management - Save/restore application settings Database Transactions - Rollback to previous state on failure

State Management Benefits

Undo/Redo Operations - Navigate through state history efficiently Save Points - Create checkpoints for later restoration Rollback Capability - Revert to known good state after errors State Isolation - Memento contents remain opaque to caretaker

Implementation Benefits

Preserves encapsulation - No external access to object internals
Simplified originator - No need to manage state history internally
Flexible storage - Caretaker can implement any storage strategy
State integrity - Immutable snapshots prevent accidental modification

Examples:
Edit document with ability to undo and redo changes through state snapshots
Input:
const { editor, history } = createTextEditorWithHistory();

editor.type('Hello');
history.save(editor.save());
editor.type(' World');
history.save(editor.save());

console.log('Current:', editor.getContent());
editor.restore(history.undo()!);
console.log('After undo:', editor.getContent());
editor.restore(history.redo()!);
console.log('After redo:', editor.getContent());
Output:
Current: Hello World
After undo: Hello
After redo: Hello World
Save game progress at checkpoints and restore when needed
Input:
const { game, saveManager } = createGameWithSaveSystem();

game.addScore(100);
game.addItem('sword');
game.move(10, 20);

// Save at checkpoint
saveManager.saveGame('checkpoint1', game.createSavePoint());

game.takeDamage(50);
game.addScore(50);
console.log('After damage:', game.getStats());

// Load checkpoint
game.loadSavePoint(saveManager.loadGame('checkpoint1')!);
console.log('After loading:', game.getStats());
Output:
After damage: {level: 1, score: 150, health: 50, inventory: ['sword'], position: {x: 10, y: 20}}
After loading: {level: 1, score: 100, health: 100, inventory: ['sword'], position: {x: 10, y: 20}}
Create named configuration snapshots for easy restoration of settings
Input:
const { config, history } = createConfigManagerWithHistory();

config.setSetting('theme', 'dark');
config.setSetting('fontSize', 16);
history.saveSnapshot(config.createSnapshot('dark-theme'));

config.setSetting('theme', 'high-contrast');
config.setSetting('fontSize', 18);

console.log('Current theme:', config.getSetting('theme'));
config.restoreSnapshot(history.getSnapshot('dark-theme')!);
console.log('Restored theme:', config.getSetting('theme'));
Output:
Current theme: high-contrast
Restored theme: dark

Concepts

design patternssoftware architecturecode organizationobject-oriented programming

Complexity Analysis

Time:O(1)
Space:O(n)

Implementation

text-editor

Time: O(1) | Space: O(n)
// Memento interface
interface TextEditorMemento {
  getContent(): string;
  getCursor(): number;
  getTimestamp(): Date;
}

// Originator
class TextEditor {
  private content = '';
  private cursor = 0;

  type(text: string): void {
    const before = this.content.substring(0, this.cursor);
    const after = this.content.substring(this.cursor);
    this.content = before + text + after;
    this.cursor += text.length;
  }

  delete(count: number = 1): void {
    const before = this.content.substring(0, Math.max(0, this.cursor - count));
    const after = this.content.substring(this.cursor);
    this.content = before + after;
    this.cursor = Math.max(0, this.cursor - count);
  }

  save(): TextEditorMemento {
    return new ConcreteTextEditorMemento(this.content, this.cursor);
  }

  restore(memento: TextEditorMemento): void {
    this.content = memento.getContent();
    this.cursor = memento.getCursor();
  }
}