Capture and externalise an object's internal state so it can be restored later — without violating encapsulation.
Memento lets you save and restore snapshots of an object's state without exposing its internal structure. The originator creates a memento containing a copy of its private state. The caretaker stores and returns mementos. The originator restores from a memento when needed.
Crucially, only the originator can read the memento's content — the caretaker stores it as an opaque token, preserving encapsulation.
You need undo in a text editor. The naive approach: make all text editor fields public so the history manager can read and save them. This violates encapsulation — internal state is exposed to the outside world.
Memento solves it: the TextEditor creates a snapshot of its own private state and returns it as an opaque object. The history manager stores it without reading it. On undo, the editor receives its snapshot back and restores itself — no outside code ever sees internal state.
// Originator — creates and restores from mementos public class TextEditor { private String text = ""; private int cursorPos = 0; private String selection = null; public void type(String chars) { text = text.substring(0, cursorPos) + chars + text.substring(cursorPos); cursorPos += chars.length(); } // Creates a snapshot — only TextEditor can read it back public Snapshot save() { return new Snapshot(text, cursorPos, selection); // captures full state } // Restores from snapshot public void restore(Snapshot snapshot) { this.text = snapshot.getText(); this.cursorPos = snapshot.getCursorPos(); this.selection = snapshot.getSelection(); } public String getText() { return text; } // ── Memento — inner class preserves encapsulation ────────── // Only TextEditor (enclosing class) can access getters public static class Snapshot { private final String text; private final int cursorPos; private final String selection; private final long timestamp; private Snapshot(String text, int cursorPos, String selection) { this.text = text; this.cursorPos = cursorPos; this.selection = selection; this.timestamp = System.currentTimeMillis(); } // Package/private access — only originator uses these private String getText() { return text; } private int getCursorPos() { return cursorPos; } private String getSelection() { return selection; } public long getTimestamp() { return timestamp; } // ok to expose public String toString() { return "Snapshot@" + timestamp + " (opaque)"; // caretaker sees this only } } } // Caretaker — manages history, never reads memento content public class History { private final Deque<TextEditor.Snapshot> undoStack = new ArrayDeque<>(); private final Deque<TextEditor.Snapshot> redoStack = new ArrayDeque<>(); private final TextEditor editor; public History(TextEditor editor) { this.editor = editor; } public void backup() { undoStack.push(editor.save()); // store snapshot — never reads it redoStack.clear(); } public void undo() { if (undoStack.isEmpty()) { System.out.println("Nothing to undo"); return; } redoStack.push(editor.save()); // save current for redo editor.restore(undoStack.pop()); // restore from snapshot } public void redo() { if (redoStack.isEmpty()) { System.out.println("Nothing to redo"); return; } undoStack.push(editor.save()); editor.restore(redoStack.pop()); } } // Client public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor(); History history = new History(editor); history.backup(); editor.type("Hello"); history.backup(); editor.type(" World"); System.out.println(editor.getText()); // Hello World history.undo(); System.out.println(editor.getText()); // Hello history.undo(); System.out.println(editor.getText()); // (empty) history.redo(); System.out.println(editor.getText()); // Hello } }
SAVEPOINT sp1 saves DB state; ROLLBACK TO sp1 restores it@Transactional rollback — on exception, transaction state is restored to startgit checkout restoresArrayDeque with a size check and evict the oldest snapshot when full// ❌ BREAKING — shallow copy, originator corrupts snapshot public class BadSnapshot { public final List<String> items; // ❌ reference to live mutable list public BadSnapshot(List<String> items) { this.items = items; } } // After save: originator.items.add("new") → snapshot is corrupted too! // ✅ FIX — deep copy at snapshot time public class GoodSnapshot { private final List<String> items; public GoodSnapshot(List<String> items) { this.items = List.copyOf(items); // ✅ immutable defensive copy } } // ❌ BREAKING — public getters expose internals to caretaker public class LeakySnapshot { public String getText() { return text; } // ❌ anyone can read public int getCursorPos() { return cursorPos; } // ❌ internals exposed } // ✅ FIX — private static inner class, only outer class accesses public class TextEditor { private String text; public static class Snapshot { private final String text; // private — caretaker cannot read private Snapshot(String t) { this.text = t; } private String getText() { return text; } // only TextEditor calls this } public Snapshot save() { return new Snapshot(text); } public void restore(Snapshot s) { this.text = s.getText(); } } // ✅ Bounded history — evict oldest when full private static final int MAX_HISTORY = 50; public void backup() { if (undoStack.size() >= MAX_HISTORY) { undoStack.removeLast(); // evict oldest to cap memory } undoStack.push(editor.save()); }
save() → Snapshot and restore(Snapshot). Snapshot is a private inner class with private fields/getters — only the enclosing Originator can access them. Caretaker holds a Stack<Snapshot>.