Editing the cart in PideYa is trial and error: the customer adds two pizzas, removes one, applies a coupon, changes the extras... and sometimes regrets it and wants to go back to how things were three changes ago. In Command we solved undo with inverse operations; but not every edit has a clean inverse, and computing one can be more fragile than the original problem. The robust alternative is to take pictures: save snapshots of the state and restore them. The obstacle is serious: to photograph the cart you would have to see its guts — and we've spent the whole course defending encapsulation. Memento resolves exactly that tension: capturing and restoring an object's state without exposing its insides to anyone.
Contents
- The problem in PideYa: undo without undressing the cart
- Intent and structure of the pattern
- Complete Java implementation
- What to save: the minimal state
- Second case: restore points when editing the menu
- Serialization and costs
- Variants
- When to use it and when not to
- Relationship with other patterns
- Common mistakes
- Exercises and conclusion
The problem in PideYa: undo without undressing the cart
The cart is a class with carefully protected internal state:
public class Cart {
private final List<OrderLine> lines = new ArrayList<>();
private String couponCode; // may be null
private BigDecimal appliedDiscount = BigDecimal.ZERO;
public void addLine(Product product, int quantity) { /* validates and adds */ }
public void removeLine(int position) { /* ... */ }
public void applyCoupon(String code) { /* validates against the service and sets the discount */ }
// read-only getters to paint the screen; no setters
}We want to "undo" the editing. The bad options:
- Getters and setters for everything:
getMutableLines(),setAppliedDiscount(...)... Anyone could then manufacture incoherent carts (a discount without a coupon, lines with zero quantity), bypassing the validations. Encapsulation — the guarantee that only the cart touches the cart's state — dies to serve a single feature. - Having the UI code copy the data: same problem under another name; plus the UI ends up coupled to every internal field —
Cartadds a new field and "undo" silently restores half-baked carts. - Per-operation inverses (the
undo()from Command): what is the exact inverse ofapplyCouponif the coupon recalculated the discount based on the lines at the time? You have to store so much context to invert it that it's almost a snapshot already... a badly taken one.
The forces in tension, precisely: we want to store the state outside the object (to restore it later) but without anyone other than the object being able to read or manufacture it. That calls for an opaque package: full of data for its owner, sealed for everyone else.
Intent and structure of the pattern
Intent (GoF): without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
Three roles with a very fine-grained distribution of trust:
classDiagram
class Cart {
-lines: List~OrderLine~
-couponCode: String
-appliedDiscount: BigDecimal
+saveState() Memento
+restore(m: Memento)
}
class Memento {
-lines: List~OrderLine~
-couponCode: String
-appliedDiscount: BigDecimal
}
class CartHistory {
-snapshots: Deque~Memento~
+save(m: Memento)
+latest() Memento
}
Cart ..> Memento : creates and reads (wide interface)
CartHistory o-- Memento : stores without looking (narrow interface)
| GoF role | In PideYa | What it sees of the memento |
|---|---|---|
| Originator (owner of the state) | Cart |
Everything: creates and reads it (wide interface) |
| Memento (the opaque snapshot) | Cart.Memento |
— |
| Caretaker (guardian that stores it) | CartHistory |
Nothing: only stores and returns it (narrow interface) |
That dual interface is the heart of the pattern: to the originator, the memento is transparent; to the caretaker (and the world), a black box with a handle. The caretaker manages when to save and which one to restore; never what is inside.
Complete Java implementation
In Java, the natural form of the dual interface is a nested class with private members: Cart can access the private fields of its nested class Memento; nobody else can.
public class Cart {
private final List<OrderLine> lines = new ArrayList<>();
private String couponCode;
private BigDecimal appliedDiscount = BigDecimal.ZERO;
/** The memento: opaque to everyone except Cart. */
public static final class Memento {
private final List<OrderLine> lines; // private: the caretaker can't see them
private final String couponCode;
private final BigDecimal appliedDiscount;
private final Instant takenAt; // useful metadata for the UI
private Memento(Cart source) { // PRIVATE constructor:
this.lines = List.copyOf(source.lines); // only Cart manufactures mementos
this.couponCode = source.couponCode;
this.appliedDiscount = source.appliedDiscount;
this.takenAt = Instant.now();
}
public Instant getTakenAt() { return takenAt; } // the only public part: metadata
}
/** Capture: an immutable picture of the current state. */
public Memento saveState() {
return new Memento(this);
}
/** Restore: go back exactly to the picture. */
public void restore(Memento memento) {
this.lines.clear();
this.lines.addAll(memento.lines);
this.couponCode = memento.couponCode;
this.appliedDiscount = memento.appliedDiscount;
}
// ... addLine, removeLine, applyCoupon as before ...
}Three details that make or break the implementation:
List.copyOf(...)at capture time: the memento stores an immutable copy, not a reference to the live list. Without this, editing the cart afterwards would also mutate "the picture" — the classic bug of the pattern. It's the same deep-copy discipline we learned in Prototype: a picture that shares guts with the original is not a picture. (Here a shallow copy of the list suffices becauseOrderLineis immutable; if it weren't, deep copy.)- Private memento constructor: nobody can manufacture fake snapshots to inject incoherent state into the cart.
- Public metadata (
takenAt): the caretaker can show the user "go back to 14:32" without seeing the contents. A narrow interface doesn't mean an empty interface.
The caretaker — notice that it works with Cart.Memento without being able to open any of them:
public class CartHistory {
private static final int MAX_SNAPSHOTS = 20;
private final Deque<Cart.Memento> snapshots = new ArrayDeque<>();
public void save(Cart.Memento memento) {
if (snapshots.size() == MAX_SNAPSHOTS) {
snapshots.removeLast(); // bounded: out with the oldest
}
snapshots.push(memento);
}
public Optional<Cart.Memento> undo() {
return Optional.ofNullable(snapshots.poll());
}
}Usage — the script is: photograph before every risky change; restore upon regret:
history.save(cart.saveState()); // picture beforehand
cart.applyCoupon("SUMMER10"); // change
history.save(cart.saveState());
cart.removeLine(0); // oops, wrong line
history.undo().ifPresent(cart::restore); // back to before removing itsequenceDiagram
participant UI as Cart screen
participant H as CartHistory (Caretaker)
participant C as Cart (Originator)
UI->>C: saveState()
C-->>UI: memento
UI->>H: save(memento)
UI->>C: removeLine(0)
Note over UI: the customer presses "undo"
UI->>H: undo()
H-->>UI: memento
UI->>C: restore(memento)
Note over H: it never looked inside the memento
What to save: the minimal state
The pattern's most important design decision isn't in the diagram: what goes into the picture? Rule: the originator's intrinsic, non-derivable state; nothing more.
| Candidate | Goes into the memento? | Why |
|---|---|---|
| Cart lines | Yes | Essential state, the source of everything |
| Applied coupon code | Yes | Can't be deduced from the lines |
| Applied discount | It depends | If it's always recomputed from coupon + lines, it's derivable: better to recompute on restore |
| Cart total | No | Pure derivative: it's computed |
Reference to CouponService |
No | Dependency, not state: it survives the undo |
| Logged-in customer data | No | Another object's state; let it photograph itself if it needs to |
Save too little → incoherent restores. Save too much → heavy, fragile mementos (the service changes and old pictures no longer fit) and even dangerous ones (card data in a snapshot?). The minimal memento is cheaper and ages better.
Second case: restore points when editing the menu
The same pattern serves the restaurant: editing the menu (the Composite of sections and dishes) is risky — reordering sections, changing prices in bulk — and the manager wants named restore points ("before the October price rise") to come back to days later. Instructive differences versus the cart:
- The state is a tree: capturing requires a deep copy of the structure (Prototype again: a recursive
clone()of the composite is the natural way to photograph it). - The snapshots must survive a restart → mementos serialized to disk/DB (next section).
- The caretaker gains substance of its own: a list of named, dated points, choosing which to restore — but it still opens none of them.
Serialization and costs
When the memento must persist or travel, it gets serialized (JSON, binary...). Warnings:
- Serialization is a back door into encapsulation: the memento's JSON exposes the fields we protected so carefully, and whoever can edit it can manufacture illegal states. Treat it as a trust boundary: when restoring from outside, revalidate (quantities are positive, the coupon still exists...).
- Versioning: October's picture must open with December's code. Add a version number to the format and a migration path, or old snapshots become time bombs.
- Memory/space cost: full snapshot × long history × large object = trouble. Mitigations: a bounded history (our
MAX_SNAPSHOTS), incremental snapshots (storing diffs — which brings you closer to Command), sharing unchanged parts between pictures (persistent data structures; if the state is immutable, "copying" is sharing references, nearly free — another dividend of the immutability we've been cultivating since the Builder).
Variants
- Memento as a private nested class + marker interface: the strict GoF version; the caretaker types against an empty
Object-like interface. Our nested class with a private constructor achieves the same with more useful types. - Originator = internal caretaker: the object itself keeps its stack of states (
cart.undo()). Less flexible (fixed saving policy) but a simpler API; reasonable when there's only one consumer of the undo. - Incremental memento: each picture stores only the delta from the previous one. Saves space, complicates restoration (you must replay the chain of deltas).
- With immutable state: if the originator is immutable (in the style of the Builder's
Order), each version is its own memento — the "history" is a list of versions and restoring means pointing at an old one. The pattern dissolves into the architecture; that's how modern UI state-management systems (Redux and friends) work.
When to use it and when not to
Use it when:
- You need to undo/restore states and inverse operations don't exist, aren't reliable, or require storing as much context as the picture itself.
- You must offer checkpoints or error recovery (capture before a risky operation, restore if it fails).
- And, the non-negotiable condition: exposing the state via getters/setters would break the encapsulation that protects the object's invariants.
Avoid it when:
- The state is large and changes often, and you can afford neither full pictures nor the complexity of deltas.
- The object is already immutable or its state is public and trivial: store references or copies without pattern liturgy.
- You only need to undo operations with an obvious, cheap inverse: plain Command is simpler.
Relationship with other patterns
- Command: the robust-undo partner — the command stores a memento of the receiver in its
execute(), and itsundo()restores it. Inverse when it's easy, picture when it's not; they combine without friction. - Prototype: capturing state is, mechanically, cloning; the deep-copy disciplines are the same.
- Iterator: an iterator can package its position in a memento to pause and resume traversals.
- State (next stop): they share the word "state" but not the problem — State changes behavior according to state; Memento photographs the data. The full clarification is in the comparison.
Common mistakes
- Pictures that share mutable structures with the original (the missing
List.copyOfor deep copy): the entire history points at the present and undo undoes nothing. It's mistake number one; always test it (capture → mutate → restore → verify). - A caretaker that opens the memento (package-private fields "for convenience", getters added "for one case"): the pattern degenerates into a public DTO and the encapsulation it came to protect vanishes.
- An unbounded history: the undo memory leak, aggravated here because each entry is a full picture.
- Storing dependencies or someone else's state in the picture: on restore, you resurrect stale references (an already-closed service, another object's state in an old version).
- Trusting deserialized mementos without revalidating: illegal state injected through the back door.
Exercises
Exercise 1: the treacherous picture
A colleague implements the capture like this: this.lines = source.lines; (no copy). Write the minimal sequence of calls (capture, mutation, restore) that demonstrates the bug, and explain what the user will observe on the cart screen.
Exercise 2: named restore points
Implement MenuHistory, the caretaker for menu editing: createPoint(String name, MenuSection.Memento m), listPoints() (name + date, for the UI dropdown), and retrieve(String name). Remember: it cannot look inside any memento. Where do the name and date it lists come from?
Exercise 3: inverse or picture?
For each restaurant-panel operation, decide whether its undo is better served by an inverse operation (pure Command) or by a memento, and justify: (a) markPreparing() ↔ backToAccepted(); (b) "rebalance prices" (raises some, lowers others, rounds, per rules that change monthly); (c) delayDelivery(minutes).
Solutions
Solution 1:
Cart.Memento shot = cart.saveState(); // "picture" (shares the live list)
cart.addLine(pizzaCarbonara, 2); // mutates the list... in the picture too
cart.restore(shot); // restores... the already-mutated stateThe restore doesn't restore: the picture pointed at the same List, so the carbonara is still in the cart. The user presses "undo" and nothing visible happens — the worst kind of bug: no exception, no log, just an undo that lies. (With the implementation nuance: restore does clear() + addAll() on that same shared list; depending on the order of operations it can even end up emptying the "picture". Every variant of the symptom springs from the same sin: not copying.)
Solution 2:
public class MenuHistory {
private record Point(String name, MenuSection.Memento memento) {}
private final Map<String, Point> points = new LinkedHashMap<>();
public void createPoint(String name, MenuSection.Memento memento) {
points.put(name, new Point(name, memento));
}
public List<String> listPoints() {
return points.values().stream()
.map(p -> p.name() + " (" + p.memento().getTakenAt() + ")")
.toList();
}
public Optional<MenuSection.Memento> retrieve(String name) {
return Optional.ofNullable(points.get(name)).map(Point::memento);
}
}The name is supplied by the caretaker (it's management metadata, not menu state); the date comes from the memento's public metadata getTakenAt() — the narrow interface allows metadata without exposing content. The actual restore is done by the originator: menu.restore(memento).
Solution 3: (a) inverse — a symmetric, cheap, stable transition: pure Command suffices; (b) memento — the "inverse" of a rebalance with changing rules and rounding is practically incomputable (roundings aren't even bijective): picture of the prices before, restore if disliked; (c) inverse (delayDelivery(-minutes)) as we saw in Command... unless the delay triggers cascading recalculations, in which case the picture wins again. Moral: inverse for what's symmetric and cheap; memento when the inverse requires reconstructing history.
Conclusion
Memento resolves a balance that seemed impossible: the cart's state goes outside — to the history, to disk — without anyone but Cart being able to read or manufacture it, thanks to the dual interface (wide for the originator, narrow for the caretaker) and the nested class with a private constructor that embodies it in Java. You also take away the decisions that separate a toy undo from a production one: truly copying at capture time, photographing the minimal non-derivable state, bounding the history, and revalidating every memento that has traveled. And the weapon-choosing rule: inverse (Command) when it's symmetric and cheap; picture (Memento) when the inverse lies or doesn't exist.
With that, the undo debts are settled. The next lesson settles a much older one — the debt of the course. In the very first lesson you saw an Order.changeStatus(...) that created the push, the SMS, and the statistics with new, and we promised a pattern would untangle that mess with elegance. Three modules later, the moment has arrived: one changes, many find out, and nobody knows anybody. See you in Observer.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What Are Design Patterns?
- History and Origin of Design Patterns
- Design Principles: SOLID and Other Foundations
- Essential UML for Understanding Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
- Introduction to Creational Patterns
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparing and Choosing Creational Patterns
Module 3: Structural Patterns
- Introduction to Structural Patterns
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparing and Choosing Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparing and Choosing Behavioral Patterns
Module 5: Applying Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring with Design Patterns
- Anti-Patterns: When Patterns Become a Problem
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Concurrency Patterns
- Design Patterns in Agile Development
