Memento Pattern
Overview
Returning to an earlier edit requires saved state. If history code reads and restores every editor field itself, an editor representation change also breaks history management.
A Memento holds a state snapshot. The originator saves and restores its own state, while a caretaker retains the snapshot without interpreting its contents.
UML
TextEditor creates a private Snapshot and returns ISnapshot. A caretaker retains that handle and returns it for restoration. TextEditor owns the state representation.
History
GoF cataloged Memento as a behavioral pattern in Design Patterns in 1994. It captures restorable state while preserving the object encapsulation.1
Why use it?
History management need not understand editor fields. The originator updates its save and restore operations when its state changes.
Retained snapshots consume storage, so define their lifetime and scope. Restoring an in-memory object does not undo already-sent mail or mutations in external storage.
Example
Save a draft, change the text and restore the snapshot.
using System;
using System.Collections.Generic;
using System.Linq;
var editor = new TextEditor();
editor.Write("draft");
ISnapshot saved = editor.Save();
editor.Write("edited");
Console.WriteLine(editor.Restore(saved));
Console.WriteLine(editor.Text);
interface ISnapshot { }
sealed class TextEditor
{
private readonly Guid id = Guid.NewGuid();
private sealed record Snapshot(Guid Owner, string Text) : ISnapshot;
public string Text { get; private set; } = "";
public void Write(string text) => Text = text;
public ISnapshot Save() => new Snapshot(id, Text);
public bool Restore(ISnapshot snapshot)
{
if (snapshot is not Snapshot saved || saved.Owner != id) return false;
Text = saved.Text;
return true;
}
}
True
draftThe caller cannot read text through ISnapshot. TextEditor interprets its own snapshot.
An extended example
Reject a snapshot supplied to the wrong editor by checking the originator identity.
using System;
using System.Collections.Generic;
using System.Linq;
var editor = new TextEditor();
editor.Write("draft");
ISnapshot saved = editor.Save();
var other = new TextEditor();
other.Write("other");
Console.WriteLine(other.Restore(saved));
Console.WriteLine(other.Text);
editor.Write("edited");
Console.WriteLine(editor.Restore(saved));
Console.WriteLine(editor.Text);
interface ISnapshot { }
sealed class TextEditor
{
private readonly Guid id = Guid.NewGuid();
private sealed record Snapshot(Guid Owner, string Text) : ISnapshot;
public string Text { get; private set; } = "";
public void Write(string text) => Text = text;
public ISnapshot Save() => new Snapshot(id, Text);
public bool Restore(ISnapshot snapshot)
{
if (snapshot is not Snapshot saved || saved.Owner != id) return false;
Text = saved.Text;
return true;
}
}
False
other
True
draftA snapshot from another originator returns false and leaves the receiver unchanged. This example restores one in-memory string; persisted snapshot formats and version compatibility require their own design.
Related reading
Each example is a separate .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Behavioral patterns, Memento. β©