Low-level designDeep dive8 min
Memento
Capture an object's state in a snapshot that only the object can read, so it can be restored later. The object saves and restores itself, which keeps its fields private and its snapshots complete as it changes.
A configurator with undo
BuildHistory saves a bike by reading its getters, and restores it through its setters.
1 · The situation
Trying a build, and going back
A bike shop's website lets customers configure a bike, frame size, gears and color, and try changes freely. A Back button returns to the last saved build, so a customer can try a red large frame and return to what they had.
The first version keeps saved builds in BuildHistory. To save, it reads each of the bike's fields through a getter into a SavedBuild; to go back, it writes each one through a setter. Python writes set_frame() and so on.
import java.util.ArrayDeque;
import java.util.Deque;
class Bike {
private String frame = "M";
private int gears = 8;
private String color = "black";
String frame() {
return frame;
}
int gears() {
return gears;
}
String color() {
return color;
}
void setFrame(String frame) {
this.frame = frame;
}
void setGears(int gears) {
this.gears = gears;
}
void setColor(String color) {
this.color = color;
}
String describe() {
return frame + " frame, " + gears + " gears, " + color;
}
}
record SavedBuild(String frame, int gears, String color) {}
class BuildHistory {
private final Deque<SavedBuild> saved = new ArrayDeque<>();
void save(Bike bike) {
saved.push(new SavedBuild(bike.frame(), bike.gears(), bike.color()));
}
void undo(Bike bike) {
SavedBuild s = saved.pop();
bike.setFrame(s.frame());
bike.setGears(s.gears());
bike.setColor(s.color());
}
}
public class Main {
public static void main(String[] args) {
Bike bike = new Bike();
BuildHistory history = new BuildHistory();
System.out.println("Start: " + bike.describe());
history.save(bike);
bike.setFrame("L");
bike.setGears(11);
bike.setColor("red");
System.out.println("Trying: " + bike.describe());
history.undo(bike);
System.out.println("Undone: " + bike.describe());
}
}The class diagram of this version, drawn from its code: the history reads the bike from outside.
A customer tries a large red frame with 11 gears, then goes back:
Start: M frame, 8 gears, black Trying: L frame, 11 gears, red Undone: M frame, 8 gears, black
2 · The change
A new option, and an undo that misses it
The shop adds a front basket as an option. The developer adds the field to Bike and the basket shows up on the page. Nobody touches BuildHistory, because nothing points to it:
class Bike {
private String frame = "M";
private int gears = 8;
private String color = "black";
private boolean basket;
String frame() {
return frame;
}
int gears() {
return gears;
}
String color() {
return color;
}
void setFrame(String frame) {
this.frame = frame;
}
void setGears(int gears) {
this.gears = gears;
}
void setColor(String color) {
this.color = color;
}
void setBasket(boolean basket) {
this.basket = basket;
}
String describe() {
return frame + " frame, " + gears + " gears, " + color + (basket ? ", basket" : "");
}
}Going back now restores three of the four options:
Start: M frame, 8 gears, black Trying: L frame, 11 gears, red, basket Undone: M frame, 8 gears, black, basket
The undone build has the old frame, gears and color, and the new basket.
The history's copy of the bike is a second description of the bike's fields, kept in a different class:
Cost | In this design |
|---|---|
A new field | Must be added to |
A missed field | Restores silently wrong, as the basket shows |
Encapsulation |
|
Fields that shouldn't be set from outside | Such as a computed price: the history can't restore them at all |
3 · The refactor
Let the bike save and restore itself
Start from the version with the basket. The bike is the only class that knows all its fields, so let it take the snapshot:
Bike.save() returns a Memento holding every field, and Bike.restore(memento) copies them back. In Java the memento is a nested class whose fields are private, so only Bike can read them; C++ makes Bike a friend; JavaScript keeps the state under a symbol only the module has; Python marks the fields private by convention.
class Bike {
private String frame = "M";
private int gears = 8;
private String color = "black";
private boolean basket;
static final class Memento {
private final String frame;
private final int gears;
private final String color;
private final boolean basket;
private Memento(String frame, int gears, String color, boolean basket) {
this.frame = frame;
this.gears = gears;
this.color = color;
this.basket = basket;
}
}
Memento save() {
return new Memento(frame, gears, color, basket);
}
void restore(Memento m) {
frame = m.frame;
gears = m.gears;
color = m.color;
basket = m.basket;
}
void setFrame(String frame) {
this.frame = frame;
}
void setGears(int gears) {
this.gears = gears;
}
void setColor(String color) {
this.color = color;
}
void setBasket(boolean basket) {
this.basket = basket;
}
String describe() {
return frame + " frame, " + gears + " gears, " + color + (basket ? ", basket" : "");
}
}The basket is restored with everything else, and the next field added to Bike will be too, as long as save() packs it:
Start: M frame, 8 gears, black Trying: L frame, 11 gears, red, basket Undone: M frame, 8 gears, black
Places a new field must be added. Before: Bike, SavedBuild, and both history methods. After: Bike and its Memento, side by side.
4 · The pattern
Memento: a snapshot only its owner can read
Memento captures an object's internal state so the object can be returned to it later, without exposing that state. The originator creates the memento and is the only one that reads it. The caretaker keeps mementos, for undo or rollback, but treats them as opaque. The memento itself is immutable.
The roles in the bike shop's code: Bike is the originator, Memento the snapshot, and BuildHistory the caretaker.
Role | In the bike shop | Its job |
|---|---|---|
Originator |
| Creates mementos of its own state, and restores from them |
Memento |
| Holds the saved state, immutable, readable only by the originator |
Caretaker |
| Keeps mementos and hands them back, without looking inside |
The caretaker passes the memento back and forth; only the bike opens it.
Two interfaces make it work. The caretaker sees a narrow one, an object it can store and pass back. The originator sees a wide one, every saved field. Java's nested class with private fields, and C++'s friend, give exactly that split.
5 · What it costs
Memory, and state that isn't only yours
- Each snapshot is a copy. A bike is four fields; a document or a game world can be megabytes. Keep a limited history, or save only what changed.
- Only the object's own state. A memento restores one object. If restoring the bike should also release a reserved part in the warehouse, the memento can't do that.
- Mutable fields need deep copies. A list of accessories saved by reference would change after the snapshot; copy it, or make it immutable.
- Opacity depends on the language. Python and JavaScript rely on convention or module privacy; nothing stops determined code from reading the state.
Situation | Memento or something else? |
|---|---|
Restore an object to an earlier state, keeping its fields private | Memento |
Undo by reversing each action, with little memory | Command, with an undo per command |
The state is already an immutable value | Just keep the old value; it is its own snapshot |
Save state to disk or send it elsewhere | Serialization, which exposes the state on purpose |
6 · Getting it right
Complete, immutable, and private
- Save everything the originator needs to restore. Put the new field in
save()andrestore()in the same change; they sit next to each other, so the pair is hard to miss. - Make the memento immutable. A caretaker that could change a memento could corrupt an undo.
- Copy what can change. Lists and other mutable objects go into the memento as copies.
- Bound the history. Keep the last few snapshots, or store differences, when states are large.
Language | Keeping the memento opaque |
|---|---|
Java | A nested class with private fields: the enclosing class can read them, nothing else can |
C++ | A nested class with private members and |
JavaScript | A frozen object whose state is stored under a |
Python | A frozen dataclass with underscore-prefixed fields: private by convention only |
7 · Look-alikes
Saving state, or reversing actions
Memento is one of several ways to go back, or to copy state:
Pattern | What it saves | Used for |
|---|---|---|
Memento | A snapshot of one object's state, opaque to others | Undo, rollback, checkpoints |
Command | The action, with how to reverse it | Undo with little memory; replay |
Prototype | A full, usable copy of an object | Creating new objects from a template |
Serialization | The state in a portable format, readable by design | Saving to disk, sending over a network |
Command and Memento are often combined: each command saves a memento of what it is about to change, and its undo() restores it, which avoids writing a reversal for every command.
8 · In practice
Snapshots behind undo and rollback
Undo stacks in editors, checkpoints in games and the rollback of a form to its last saved values all keep snapshots taken by the object that owns the state. The common mistake is the one in this story: a snapshot built from outside, field by field, that goes stale the first time a field is added.
The practice checks have no Memento detector, and all three versions read as clean: the missing basket is a behavior bug the checks can't see. This page shows it in the output instead.
Check yourself
4 questions
Takeaways
Remember this
- Memento captures an object's state so it can be restored later, without exposing it.
- The originator saves and restores itself; the caretaker keeps snapshots it cannot read.
- Snapshots built from outside, field by field, go stale when a field is added.
- Make mementos immutable, copy mutable fields, and bound the history.
- Memento saves state; Command saves actions. Both can implement undo.