🎮 Analogy
A video game save point captures your entire state — position, inventory, health — in one blob. Hours later you reload it and you’re exactly where you were. You never see the save file’s internals; you just save and restore. That blob is a memento.
The problem
You want undo, checkpoints, or rollback — which means capturing an object’s state and restoring it later. But you don’t want the thing doing the saving (the caretaker) to rummage through the object’s private fields. Memento puts the snapshot in an opaque object: the originator creates and reads it; everyone else just holds it.
Structure
classDiagram
class Editor {
-text string
+Save() memento
+Restore(memento)
}
class memento {
-text string
}
class History {
-stack memento[]
+Push(memento)
+Pop() memento
}
Editor ..> memento : creates & reads
History o--> memento : stores (opaque)Idiomatic Go
memento has unexported fields, so History (the caretaker) can stack snapshots without reading them — only the Editor can. Edit and Run:
package main
import "fmt"
// memento: an opaque snapshot. Unexported fields keep it private.
type memento struct{ text string }
// Originator.
type Editor struct{ text string }
func (e *Editor) Type(s string) { e.text += s }
func (e *Editor) Text() string { return e.text }
func (e *Editor) Save() memento { return memento{text: e.text} }
func (e *Editor) Restore(m memento) { e.text = m.text }
// Caretaker: keeps history, treats mementos as opaque tokens.
type History struct{ stack []memento }
func (h *History) Push(m memento) { h.stack = append(h.stack, m) }
func (h *History) Pop() (memento, bool) {
if len(h.stack) == 0 {
return memento{}, false
}
m := h.stack[len(h.stack)-1]
h.stack = h.stack[:len(h.stack)-1]
return m, true
}
func main() {
ed := &Editor{}
hist := &History{}
ed.Type("Hello")
hist.Push(ed.Save()) // checkpoint
ed.Type(", World")
hist.Push(ed.Save()) // checkpoint
ed.Type("!!!")
fmt.Println("now: ", ed.Text()) // Hello, World!!!
if m, ok := hist.Pop(); ok {
ed.Restore(m)
fmt.Println("undo 1:", ed.Text()) // Hello, World
}
if m, ok := hist.Pop(); ok {
ed.Restore(m)
fmt.Println("undo 2:", ed.Text()) // Hello
}
}
🐹 Encapsulation by package, undo by Command
Go has no private keyword — encapsulation is per package. Unexported memento fields mean the caretaker can hold snapshots but not peek inside, which is exactly the pattern’s intent. Memento pairs naturally with Command for undo (a command snapshots state before acting) and shares the Prototype deep-copy caveat: if your state has slices or maps, the snapshot must copy them, not alias them.
Three ways to undo
Memento is one of three strategies for “go back,” and they trade memory for complexity:
| Approach | Stores | Best when |
|---|---|---|
| Memento (full snapshot) | a complete state copy per checkpoint | state is small, or restore must be exact and simple |
| Diff / delta | only what changed at each step | state is large but edits are local |
Command Undo | how to reverse each action | actions are cleanly invertible (toggle, add/remove) |
In Go, a snapshot can be a small struct of unexported fields (as above), or — for arbitrary graphs — a gob/json round-trip (slow, drops unexported fields, but generic). The opacity that defines Memento comes for free from Go’s package-level encapsulation.
Pitfalls
⚠️ Full snapshots can get expensive
Saving the whole state on every checkpoint is simple but memory-hungry for large objects or long histories. When that bites, store diffs (what changed) instead of full copies, cap the history depth, or use Command’s Undo to reverse actions rather than restore snapshots.
When to use it — and when not
✅ Reach for it when
- You need undo/redo, checkpoints, or snapshots of an object's state.
- You want to roll back to a previous state after a failed operation (transactions).
- The state should be saved without exposing the object's internals to the saver.
⛔ Think twice when
- The state is huge — full snapshots cost too much memory; store diffs or use Command to reverse actions.
- The object is trivial — saving and restoring a field by hand is simpler.
Related patterns
Turn a request into a standalone object, letting you parameterize, queue, log, and undo operations.
creationalPrototypeCreate new objects by cloning an existing, configured instance instead of building one from scratch.
behavioralStateLet an object alter its behavior when its internal state changes, as if it changed class.
Check your understanding
Score: 0 / 51. What does Memento preserve while saving state?
The memento captures internal state in an object whose contents only the originator can read, so the caretaker can store/restore it without seeing inside.
2. In Go, what enforces the memento's opacity?
Go's encapsulation is at the package level. With unexported memento fields, the caretaker can hold mementos but can't inspect or tamper with them.
3. Which pattern commonly pairs with Memento for undo?
Command + Memento is the classic undo combo: the command captures a snapshot (or enough state) before acting, so Undo can restore it.
4. Your editor state holds a []string. What must Save() do for a correct memento?
A slice field is a header sharing a backing array. If the memento just copies the header, editing the live document also corrupts the snapshot. Memento inherits Prototype's deep-copy caveat: duplicate slices/maps (slices.Clone, etc.) when snapshotting.
5. When should you store diffs (or use Command's Undo) instead of full mementos?
Full snapshots are simple but O(state size) per checkpoint. For big documents or deep undo stacks, store just what changed (a diff) or reverse the action via Command.Undo, which often needs far less memory than a whole snapshot.
Comments
Sign in with GitHub to join the discussion.