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.

Watch it evolve1 / 8
The problem

A configurator with undo

BuildHistory saves a bike by reading its getters, and restores it through its setters.

80
Start
Stage 1
Stage 2
Final

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.

The first versionJava
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());
    }
}
*Bike− frame: String− gears: int− color: String~ frame(): String~ gears(): int~ color(): String~ setFrame(String)~ setGears(int)~ setColor(String)~ describe(): String«record»SavedBuild− frame: String− gears: int− color: StringBuildHistory− saved: Deque<SavedBuild>~ save(Bike)~ undo(Bike)

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:

The basket, added to BikeJava
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 Bike, to SavedBuild, and to both history methods

A missed field

Restores silently wrong, as the basket shows

Encapsulation

Bike needs a public setter for every field, only so the history can write them back

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.

Java
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" : "");
    }
}
«create»*Bike− frame: String− gears: int− color: String− basket: boolean~ save(): Memento~ restore(Memento)~ setFrame(String)~ setGears(int)~ setColor(String)~ setBasket(boolean)~ describe(): StringMemento− frame: String− gears: int− color: String− basket: booleanBuildHistory− saved: Deque<Bike.Memento>~ save(Bike)~ undo(Bike)

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
Field by field from outside
4
Bike saves itself
2

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.

«create»*Bike− frame: String− gears: int− color: String− basket: boolean~ save(): Memento~ restore(Memento)~ setFrame(String)~ setGears(int)~ setColor(String)~ setBasket(boolean)~ describe(): StringMemento− frame: String− gears: int− color: String− basket: booleanBuildHistory− saved: Deque<Bike.Memento>~ save(Bike)~ undo(Bike)

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

Bike

Creates mementos of its own state, and restores from them

Memento

Memento

Holds the saved state, immutable, readable only by the originator

Caretaker

BuildHistory

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() and restore() 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 friend class Bike

JavaScript

A frozen object whose state is stored under a Symbol the module doesn't export

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

1. The shop adds a computed price that customers can't set. How does undo restore it after the refactor?
2. Why is the memento immutable?
3. Accessories are kept in a list. What must save() do with it?
4. When is Command a better fit than Memento for undo?

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.