iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…