Skip to content

// Design Patterns · 18

Memento

Need undo or rollbackleads toSnapshot state, restore later

Capture an object's state to restore later, without exposing it.

How it fits together

Memento structure«client»QuoteEditorEdit()Undo()QuoteDraftoriginatorSave()Restore(snapshot)EditHistorycaretakerstack, last 50«record»QuoteSnapshotmementoopaque, immutablePush / PopSave / RestoreSave()keeps
uses / callscreateswraps / holds
  • QuoteEditor uses / calls EditHistory (Push / Pop)
  • QuoteEditor uses / calls QuoteDraft (Save / Restore)
  • QuoteDraft creates QuoteSnapshot (Save())
  • EditHistory wraps / holds QuoteSnapshot (keeps)

Spot it

  • Undo implemented by copying public fields
  • Users want to revert a draft to a saved point
  • Fields made public only so they can be saved

It costs

Snapshots cost memory; large or frequent ones need limits or diffs.

In code

public sealed record QuoteSnapshot{    internal QuoteSnapshot(ImmutableList<QuoteLine> lines, decimal discount) =>        (Lines, Discount) = (lines, discount);​    internal ImmutableList<QuoteLine> Lines { get; }    internal decimal Discount { get; }}​public sealed class QuoteDraft{    private ImmutableList<QuoteLine> _lines = [];    private decimal _discount;​    public QuoteSnapshot Save() => new(_lines, _discount);​    public void Restore(QuoteSnapshot snapshot) =>        (_lines, _discount) = (snapshot.Lines, snapshot.Discount);}​public sealed class EditHistory{    private readonly Stack<QuoteSnapshot> _undo = new();​    public void Push(QuoteSnapshot snapshot) => _undo.Push(snapshot);    public QuoteSnapshot? Pop() => _undo.TryPop(out var snapshot) ? snapshot : null;}
Go deeperThe full explanation and what to ask for · 1 min readExpand

Three roles: the originator creates a memento of its own state and can restore from one; the memento is an opaque, immutable snapshot; the caretaker stores mementos and hands them back but never looks inside. Undo works without making the originator's fields public.

Full snapshots of large objects get expensive fast, so cap the history, snapshot only what changes, or store diffs. A snapshot that references mutable objects is not a snapshot; copy them. And a memento saved to storage becomes a versioned format that needs migrating.

Command undoes by reversing each action, Memento by restoring a saved state, and the two are often combined. Prototype also copies state, but to make a new object rather than roll one back. Event sourcing rebuilds state by replaying events and uses snapshots only as a shortcut.

Ask for it

  • originator
  • memento
  • caretaker
  • snapshot
  • undo stack
  • encapsulation

Add undo to the quote editor: QuoteDraft creates an immutable QuoteSnapshot of its lines and discount with Save() and restores one with Restore(snapshot). An EditHistory keeps the last 50 snapshots on a stack and never reads inside them; the editor cannot change a snapshot.