Behavioral Patterns
Memento
Capture and restore an object's internal state without violating encapsulation, allowing undo/redo operations.
The Memento Pattern is a behavioral design pattern that allows you to capture the current state of an object and save it externally so it can be restored later—all without exposing the object's internal structure or violating its encapsulation. It is the architectural foundation for undo/redo stacks, history tracking, and transaction checkpointing.
1. Learning Objectives
- Understand how the Memento pattern captures and restores state without breaking the Single Responsibility or Open/Closed principles.
- Learn to enforce encapsulation boundary limits using private nested classes (Java), friend relationships (C++), and underscore naming conventions (Python).
- Differentiate between Memento, Command, and State patterns.
- Analyze the memory trade-offs of storing full state snapshots versus incremental state diffs in memory-sensitive systems.
- Implement a thread-safe, history-limited undo/redo manager.
2. Problem & Naive Solution
Imagine you are building a rich text editor. The editor supports operations like typing text, moving the cursor, and changing font sizes. To make the application user-friendly, you need to implement a history manager that supports the classic Undo (Ctrl+Z) and Redo (Ctrl+Y) operations.
The Naive Solution
To save the editor's state, an external history manager needs to grab the editor's contents and state, store them in a history list, and restore them when needed. A naive implementation directly exposes all internal fields of the editor:
3. Issues with the Naive Approach
- Encapsulation Violation: The
Editormust make all of its internal state representation variables public (or provide public getters and setters). Any other class in the application can read or modify the editor's text and cursor coordinates, exposing internal data models. - Tight Coupling: The
EditorHistoryclass is tightly coupled to the internal variables ofEditor. If you rename a variable (e.g., changingcursorXtocursorCol), or add a new attribute (likeselectionRange), you must modify the backup and restore logic insideEditorHistory. - Fragile Integrity: An external caretaker class can inadvertently mutate state variables while restoring, or restore an invalid combination of variables, causing the editor to enter an inconsistent runtime state.
4. Pattern Introduction & UML
The Memento Pattern solves this problem by delegating the responsibility of capturing and restoring state back to the object that owns the state (the Originator). It introduces a third object called the Memento, which is a read-only token representing a snapshot of the originator's state. The Caretaker (history manager) holds the mementos but is strictly blocked from reading or altering the contents inside the memento.
UML Diagram
5. Participants
- Originator (
Editor): The object whose state needs to be saved. It creates a Memento containing a snapshot of its current state and uses the Memento to restore its state when requested. - Memento (
EditorMemento): The value object containing the state snapshot. It provides a wide interface to the Originator (allowing full access to its stored properties) and a narrow interface to the Caretaker (allowing no access to the stored state). - Caretaker (
CommandHistory): The client that knows *when* and *why* to save or restore the Originator's state. It manages a stack of Mementos but never accesses or inspects the details within them.
6. Theory: Design Choices & Pattern Comparisons
When implementing state recovery systems, you must make a conscious trade-off between the Memento, Command, and State patterns:
| Pattern | Primary Focus | State Management | Typical Use Case |
|---|---|---|---|
| Memento | Capturing raw values of object states at specific time checkpoints. | Originator produces immutable snapshots. Caretaker stores them. | Undo stacks, point-in-time recovery, transactions. |
| Command | Encapsulating actions/operations as objects. | Actions implement an execute() and an inverse unexecute() method. |
Routing actions, logging requests, action-based undo queues. |
| State | Changing an object's behavior when its internal state changes. | Delegating dynamic behavior to separate state class implementations. | State machines, TCP connections, media players. |
Comparing Memento Undo vs. Command Undo
In a Command-based undo system, the history stack stores the operations performed (e.g., "Insert char 'A' at pos 5"). To undo, the system calls unexecute(), which performs the inverse mathematical operation (e.g., "Delete char at pos 5"). While memory-efficient, this requires complex, bug-prone inverse calculations for every operation.
Conversely, Memento-based undo systems save a direct state snapshot. Restoring is as simple as overriding variables, making it highly reliable for complex states but introducing higher memory usage.
7. Syntax & Encapsulation Enforcement
To successfully implement Memento, you must ensure that only the Originator can access the Memento's state. Different languages offer distinct mechanisms to enforce this encapsulation:
-
Java (Private Nested Classes): The Memento class is defined as a public or package-private class with only
privatemembers, nested inside the Originator. The outer Originator class has full access to the nested class's private members, but other classes cannot access them. -
C++ (Friend Classes): The Memento class defines all its constructors and methods as
private, but declares the Originator class as afriend class. This restricts instantiation and inspection privileges solely to the Originator. -
Python (Conventions & Private Attributes): Python does not support language-level access modifiers. Encapsulation is achieved by prefixing the Memento class and its properties with a single underscore (
_EditorMemento,_content), indicating they are private internals that external objects must not touch.
8. Step-by-Step Implementation
- Define the Memento Interface/Reference Type: Create a marker interface or reference type that represents the memento to the caretaker.
- Design the Originator: Add the state fields to the Originator class, along with the standard mutations.
- Implement the Memento Nested Class: Make this class immutable (all fields declared
final/const). The Memento constructor receives the current values of the Originator's state. - Add Save/Restore hooks on the Originator: Create a
save()method that instantiates the Memento with its current fields, and arestore(Memento)method that extracts the saved state. - Create the Caretaker: Implement the history stack. The Caretaker calls
originator.save()before modifying the state and pushes the returned Memento to its history stack. To perform an undo, it pops from the stack and callsoriginator.restore(memento).
9. Complete Code (Mini Project)
Below is a complete implementation of a text editor application with multiple states (text content, cursor position, and current document theme) and an undo/redo manager.
10. Code Walkthrough
Let's walk through the key components of the Java implementation:
- Nested Class Structure: Notice that
EditorMementois defined directly insideTextEditor. SinceEditorMemento's fields and constructor areprivate, no class other thanTextEditorcan instantiate it or extract its private attributes. - Narrow Interface to Caretaker: The
HistoryManagerreceives instances ofTextEditor.EditorMemento. However, because it has no access toEditorMemento's internal structure, the history manager is treated as an opaque custodian—it holds the token but cannot inspect it. - Wide Interface to Originator: Within the
TextEditor.restore()method, the outer class reads the private fields (memento.text,memento.cursorX, etc.) directly, enabling seamless state overrides. - Standard Redo Eviction: Whenever
backup()is invoked (meaning the user has typed something new), the redo stack is wiped (redoStack.clear()), ensuring standard, linear branch history.
11. Execution Flow
- Setup: The caretaker (
HistoryManager) binds to the originator (TextEditor). - State Modification Prep: Before executing a change (like
.type()), the client callshistory.backup(). - Snapshot Creation:
history.backup()triggerseditor.save(), which creates a newEditorMementoobject on the heap containing deep or immutable copies of the editor's text, cursor position, and theme. This Memento is pushed to the caretaker'sundoStack. - Execution: The editor's state changes.
- Undo Invocations: The client calls
history.undo(). The caretaker saves the current editor state to theredoStack(for potential redo), pops the top memento from theundoStack, and forwards it toeditor.restore(previousState). - Restoring: The editor extracts the variables directly from the memento, resetting its state.
12. Internal Working (JVM Heap & Stack)
Let's visualize the runtime memory allocation inside the JVM when using the Memento Pattern:
- Originator Reference: The
TextEditorinstance resides on the heap. Its fields reference primitive values or strings (which are stored in the String Constant Pool or heap). - Caretaker Reference Stack: The
HistoryManagerholds aStackcollection object on the heap. The stack elements are object references pointing to differentEditorMementoinstances. - Immutable Objects: Since
EditorMementoonly containsfinalvariables, once it is constructed on the heap, its values cannot be altered. If the caretaker pops an item and restores the originator, and that snapshot is no longer in any list, the garbage collector will mark that memento instance for deletion on its next pass. - String Reuse: Java's immutable Strings are highly advantageous here. Storing multiple mementos containing strings that do not change avoids duplicating character arrays on the heap, optimizing memory.
13. Complexity Analysis
-
Time Complexity:
save(): $O(S)$ where $S$ is the size of the state. If copying strings or arrays, it scales with state length. For primitive properties, it is $O(1)$.restore(): $O(S)$ to copy values back to the originator.undo()/redo()stack push/pop: $O(1)$.
- Space Complexity: $O(N \cdot S)$ where $N$ is the number of saved history states and $S$ is the size of each state. If history is unbounded, it can lead to high memory consumption.
14. Best Practices
- Enforce Immutability: Memento states must be immutable. Ensure all fields are private and final, and do not provide setter methods.
- Bound the Caretaker History: Never use an unbounded stack in production. Set a maximum size (e.g., limit history to 50 operations) and eject oldest states using a deque to prevent memory leaks.
- Implement State Serialization: In large desktop apps or game design, serialize the Memento objects to disk or file database to release RAM during long-running sessions.
- Consider Incremental Diffs: If the state size is large (e.g., high-definition canvases or documents), store only the incremental diffs (deltas) between states instead of full snapshots to reduce memory usage.
15. Common Mistakes
- Exposing Public Memento API: Exposing getters/setters on the Memento class, allowing other parts of the application to read and modify the saved states directly.
- Deep Copy Omissions: Saving mutable objects (like arrays or maps) in the Memento without copying them first. If the originator modifies the list after saving, the saved snapshot will change too.
- Heap Overflow: Keeping millions of full-state snapshots in memory indefinitely without setting history limits.
16. Framework & Real-world Usage
- Java Swing Undo/Redo: The swing packages utilize
javax.swing.undo.UndoableEditandUndoManagerto capture, track, and restore states of UI components. - Database Transactions: Databases implement Savepoints. When you create a savepoint in SQL (
SAVEPOINT sp1), the transaction manager checkpoints the state. You can revert changes usingROLLBACK TO sp1without aborting the entire transaction. - Git Version Control: Git commits act as Mementos. Each commit represents a point-in-time snapshot of the repository, managed by a branch reference history (Caretaker).
17. Interview Discussion
Answer: You should employ structural sharing (copy-on-write) or serialization. For example, instead of duplicating large nested objects, reference immutable nodes. If a child node changes, recreate only the changed path, sharing the remaining unmodified node graphs between mementos.
Answer: Yes, this is known as a marker interface pattern. The Memento class implements a blank interface (
Memento). The Caretaker only interacts with this marker interface. When passing the interface to the Originator, the Originator downcasts the interface back to the concrete Memento class to access its private members, protecting the fields from being read by the caretaker.
Answer: Instead of using a simple linear stack, you model the caretaker's history as a Directed Acyclic Graph (DAG) or a state tree (like Git branches). Restoring states involves moving the current pointer to different nodes in the history graph rather than popping them permanently.
18. Practice Exercises
- Easy: Build a basic calculator with an undo feature that rolls back calculations (
add,subtract,multiply,divide). - Medium: Design a
ProfileSettingsbackup wizard where a user configures their profile across 3 steps. Let them go back to any step to restore previous settings. - Hard: Implement a multi-state Board Game History manager (like Chess). The board should store positions, active players, and timers, and support traversing back and forth through moves.
19. Challenge Problem
Design an In-Memory Database Transaction Manager. The database contains key-value pairs. Write a system that supports nested transactions:
begin(): Starts a transaction.put(key, value): Inserts or updates a value.commit(): Merges the current transaction changes into the database.rollback(): Reverts the database state to the point immediately beforebegin().
Make sure your transaction manager allows nested transactions (e.g., calling begin(), performing writes, starting another transaction, and rolling back only the inner transaction). Use the Memento Pattern to backup database states at transaction boundaries.
20. Summary & Cheat Sheet
| Concept | Rule of Thumb |
|---|---|
| Originator | The class containing the active state. Handles saving and restoring its own fields. |
| Memento | The immutable snapshot value object. Hidden from external classes. |
| Caretaker | The manager class holding the history stack. Has no access to state values. |
| Encapsulation | Enforced by nested class declarations in Java, friend functions in C++. |
| Memory Limit | Always bound Caretaker collections with a maximum size to avoid OutOfMemory errors. |
21. Quiz
1. What is the primary purpose of the Memento pattern?
A) To dynamically wrap objects and extend behavior
B) To capture and restore an object's internal state without violating encapsulation (Correct)
C) To implement a state machine mapping transitions
2. Which participant in the Memento pattern has access to the saved state properties?
A) Caretaker
B) Originator (Correct)
C) Command Manager
3. Why should a Memento object be immutable?
A) To allow concurrent access and prevent accidental modification of saved historical states (Correct)
B) To speed up JVM garbage collection
C) To satisfy compiler requirements
4. How does Java enforce encapsulation boundaries for the Memento class?
A) By declaring the class private and nesting it inside the Originator class (Correct)
B) By declaring all variables public
C) By extending the ThreadLocal interface
5. How does C++ restrict Memento access to the Originator class?
A) By making all variables public
B) By utilizing the friend class keyword inside the Memento class (Correct)
C) By using dynamic casting
6. What is the time complexity of pushing a new state to a caretaker's stack?
A) $O(N)$
B) $O(\log N)$
C) $O(1)$ (Correct)
7. What is a key disadvantage of the Memento pattern compared to the Command pattern for undo tracking?
A) High memory consumption due to storing full state snapshots rather than operation logs (Correct)
B) Slower execution flow on restoring states
C) Breaking polymorphism
8. When should you use incremental diffs (deltas) instead of full snapshots in Memento?
A) When the originator state size is extremely large and saving full copies would exhaust heap space (Correct)
B) When you only have one undo state
C) When implementing the pattern in Python
9. Which real-world tool behaves structurally like the Memento pattern?
A) Git version control commits (Correct)
B) JSON parser
C) HTTP Router
10. What mistake leads to a memory leak in a caretaker object?
A) Using an unbounded stack to store snapshots indefinitely without setting a maximum capacity limit (Correct)
B) Making memento fields final
C) Declaring the nested class static
22. Next Lesson Preview
This completes our deep-dive into Behavioral Design Patterns. In the next module, we will explore Module 11 — Concurrency & Multithreading. We will learn how to design thread-safe applications, understand thread execution lifecycles, and handle memory visibility issues in multi-threaded CPU environments!
Related Topics
- StrategyDefine a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients.
- IteratorAccess elements of an aggregate object sequentially without exposing its underlying representation (list, stack, tree, graph).
- ObserverDefine a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.