The Problem
Undo and rollback are useful, but exposing every field of an object just so external code can save snapshots is risky. The object should keep control of its internals while still allowing state to be captured and restored at safe points.
The Solution
Memento lets the originator create opaque snapshots of its own state. In Go, the snapshot is usually an ordinary value with a narrow purpose, and the originator owns how that value is created and restored. A caretaker stores the snapshots without needing to understand the internals.
Structure
The Originator
ShoppingCart is the originator. It owns the mutable state — line items, quantities, applied coupons — and knows how to create a deep-copy snapshot of itself without leaking internal structure.
flowchart LR Client["Client"] Cart["ShoppingCart originator"] History["CartHistory caretaker"] Snap["CartMemento snapshot"] Client -->|"Save()"| Cart Cart -->|"CreateSnapshot()"| Snap Client -->|"Push(snap)"| History History -->|"stores"| Snap Client -->|"Restore(Pop())"| Cart History -->|"Pop()"| Snap
- Originator:
ShoppingCartowns the mutable state. - Memento:
CartMementostores a snapshot of lines and coupon state. - Caretaker:
CartHistorypushes and pops snapshots for undo. - Client: Decides when to save or restore checkpoints.
Implementation
This example snapshots a shopping cart before applying changes. The cart controls how deep copies are made, and the history object simply stores checkpoints for rollback.
package main
type CartLine struct {
SKU string
Qty int
}
type CartMemento struct {
lines []CartLine
coupon string
} Real-World Analogy
Think of a video game save point. The player can capture the current progress and return to it later, but the save file does not need to expose every internal engine detail to the player.
Pros and Cons
| Pros | Cons |
|---|---|
| Enables rollback and undo without exposing internals broadly. | Snapshots can become expensive in memory or copy time. |
| Keeps snapshot creation under the originator’s control. | Deep-copying mutable state correctly takes care. |
| Makes checkpoint-based recovery explicit. | Works poorly when state is huge or tied to external resources. |
Best Practices
- Let the originator define snapshot boundaries so internal invariants stay protected.
- Copy mutable slices or maps deeply when the memento needs isolation.
- Store snapshots only at meaningful checkpoints; full-history snapshots can be expensive.
- Use Memento for explicit rollback, not as a substitute for persistent storage.
- Keep the memento focused on state restoration rather than turning it into a generic DTO.
When to Use
- You need undo or rollback without exposing internals broadly.
- State can be snapshotted at clear moments in a workflow.
- The object should control how restoration happens.
When NOT to Use
- State is too large to snapshot cheaply and fine-grained commands are a better fit.
- Rollback can be modeled more simply through append-only events or command undo.
- The snapshot object would leak too much of the originator internals anyway.