Table of Contents
- What is the Memento Design Pattern?
- Core Participants in the Memento Pattern
- Real-World Analogy
- Python Implementation: A Text Editor Example
- Benefits of Memento in Python
- Drawbacks and Considerations
- Advanced Use Case: Adding Redo Functionality
- When to Use the Memento Pattern
- Conclusion
- References
What is the Memento Design Pattern?
The Memento pattern is a behavioral design pattern that enables an object (called the “Originator”) to capture its internal state at a specific point in time and save it externally (in a “Memento” object). Later, the Originator can restore its state from this Memento without exposing the details of how its state is stored or managed.
Intent
- Capture and externalize an object’s internal state so it can be restored later.
- Ensure the Originator’s state remains encapsulated (i.e., the Caretaker or other objects cannot modify the Memento directly).
Core Participants in the Memento Pattern
The pattern involves three key components:
1. Originator
The object whose state needs to be saved and restored. It:
- Creates a Memento containing a snapshot of its current state.
- Restores its state from a given Memento.
2. Memento
A passive object that stores the Originator’s state. It:
- Only the Originator can write data to the Memento (ensuring encapsulation).
- Provides read-only access to its stored state (so other objects can’t modify it).
3. Caretaker
Manages the Memento’s lifecycle. It:
- Stores Mementos (e.g., in a stack, list, or database) to keep track of state history.
- Passes Mementos back to the Originator when state restoration is needed (e.g., “undo”).
Real-World Analogy
Think of a word processor like Microsoft Word. When you type, Word automatically saves “snapshots” of your document (Mementos) in an undo buffer (Caretaker). If you make a mistake, hitting “Undo” tells the document (Originator) to restore its state from the last snapshot (Memento) in the buffer. The Caretaker (undo buffer) doesn’t know how the document stores its content—it just holds the snapshots.
Python Implementation: A Text Editor Example
Let’s implement a simple text editor with undo functionality using the Memento pattern. We’ll break it down into the three participants: TextEditor (Originator), TextEditorMemento (Memento), and UndoManager (Caretaker).
Step 1: Define the Memento (TextEditorMemento)
The Memento will store the text editor’s state (current content) and provide read-only access to it.
class TextEditorMemento:
def __init__(self, state):
# Store the Originator's state (content)
self._state = state # Private to prevent external modification
def get_state(self):
# Read-only access to the state
return self._state
Step 2: Define the Originator (TextEditor)
The TextEditor will manage its content, create Mementos to save state, and restore state from Mementos.
class TextEditor:
def __init__(self):
self.content = "" # The state to be saved/restored
def type(self, text):
# Modify the state (e.g., user typing)
self.content += text
def create_memento(self):
# Create a Memento with the current state
return TextEditorMemento(self.content)
def restore_from_memento(self, memento):
# Restore state from a Memento
self.content = memento.get_state()
Step 3: Define the Caretaker (UndoManager)
The UndoManager will track Mementos in a stack to enable undo operations.
class UndoManager:
def __init__(self):
self._mementos = [] # Stack to store Mementos
def save_state(self, originator):
# Save the Originator's current state by creating a Memento
memento = originator.create_memento()
self._mementos.append(memento)
def undo(self, originator):
# Restore the Originator's state from the last Memento
if not self._mementos:
print("Nothing to undo!")
return
last_memento = self._mementos.pop() # Get the most recent Memento
originator.restore_from_memento(last_memento)
Step 4: Use the Pattern
Let’s test the workflow: type text, save state, then undo to restore.
# Initialize components
editor = TextEditor()
undo_manager = UndoManager()
# User types and saves state
editor.type("Hello, ")
undo_manager.save_state(editor) # Save after "Hello, "
editor.type("World!")
print("After typing: ", editor.content) # Output: After typing: Hello, World!
# Undo the last action
undo_manager.undo(editor)
print("After undo: ", editor.content) # Output: After undo: Hello,
How It Works
- When the user types
"Hello, ",editor.contentbecomes"Hello, ". TheUndoManagersaves this state by callingeditor.create_memento(), which returns aTextEditorMementowith the content"Hello, ". - The user then types
"World!", makingeditor.content"Hello, World!". - When
undo()is called, theUndoManagerpops the last Memento (with state"Hello, ") and passes it toeditor.restore_from_memento(), reverting the content.
Benefits of Memento in Python
Python’s flexibility amplifies the Memento pattern’s strengths:
1. Encapsulation
The Originator’s state (TextEditor.content) is hidden from the Caretaker (UndoManager). The Memento’s _state is private, so only the Originator can modify it—ensuring data integrity.
2. Simplicity
Python’s class system makes defining Mementos trivial. Unlike languages with strict access modifiers (e.g., Java), Python uses naming conventions (e.g., _state for “private” attributes) to enforce encapsulation without boilerplate.
3. Lightweight Mementos
For simple states, you can even use tuples or dataclasses (Python 3.7+) to create Mementos. For example, a dataclass Memento:
from dataclasses import dataclass
@dataclass(frozen=True) # Immutable to prevent modification
class TextEditorMemento:
state: str # Read-only by default in frozen dataclasses
Drawbacks and Considerations
1. Memory Overhead
Storing many large Mementos (e.g., snapshots of a 100-page document) can consume significant memory. Use strategies like limiting history size (e.g., keep only the last 100 states) to mitigate this.
2. Mutable State Risks
If the Originator’s state includes mutable objects (e.g., lists, dictionaries), the Memento may store a reference to the object, not a copy. Modifying the original object later will alter the Memento’s state!
Fix: Use deep copies for mutable states:
import copy
class TextEditor:
def __init__(self):
self.content = [] # Mutable state (list of paragraphs)
def create_memento(self):
# Deep copy to avoid reference issues
return TextEditorMemento(copy.deepcopy(self.content))
Advanced Use Case: Adding Redo Functionality
To support “redo” (restoring a state after undoing), extend the Caretaker to track two stacks: one for undo history and one for redo history.
Step 1: Update the Caretaker (UndoRedoManager)
class UndoRedoManager:
def __init__(self):
self._undo_stack = [] # Mementos for undo
self._redo_stack = [] # Mementos for redo
def save_state(self, originator):
# When a new state is saved, clear redo history (new action invalidates redo)
self._redo_stack.clear()
self._undo_stack.append(originator.create_memento())
def undo(self, originator):
if not self._undo_stack:
print("Nothing to undo!")
return
# Save current state to redo stack before undoing
current_memento = originator.create_memento()
self._redo_stack.append(current_memento)
# Restore from last undo memento
last_undo_memento = self._undo_stack.pop()
originator.restore_from_memento(last_undo_memento)
def redo(self, originator):
if not self._redo_stack:
print("Nothing to redo!")
return
# Save current state to undo stack before redoing
current_memento = originator.create_memento()
self._undo_stack.append(current_memento)
# Restore from last redo memento
last_redo_memento = self._redo_stack.pop()
originator.restore_from_memento(last_redo_memento)
Step 2: Test Undo/Redo
editor = TextEditor()
manager = UndoRedoManager()
editor.type("Hello")
manager.save_state(editor) # undo_stack: [memento1 ("Hello")]
editor.type(" World")
manager.save_state(editor) # undo_stack: [memento1, memento2 ("Hello World")]
manager.undo(editor) # undo_stack: [memento1], redo_stack: [memento2]
print("After undo: ", editor.content) # Output: Hello
manager.redo(editor) # undo_stack: [memento1, memento2], redo_stack: []
print("After redo: ", editor.content) # Output: Hello World
When to Use the Memento Pattern
Use Memento when:
- You need undo/redo functionality (e.g., text editors, graphic design tools).
- You need to save/load object states (e.g., game progress, configuration backups).
- You want to isolate the Originator’s state from its history management (separation of concerns).
Conclusion
The Memento pattern is a powerful tool for state management, and Python’s simplicity makes it easy to implement. By separating state capture (Originator), storage (Memento), and history (Caretaker), you can build robust systems with undo/redo, save/load, and other stateful features—all while keeping your code clean and encapsulated.
Whether you’re building a simple app or a complex system, Memento helps you “save the day” (and the state) when things go wrong.
References
- Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley.
- Refactoring Guru: Memento Pattern
- Python Dataclasses Documentation
- Real Python: Design Patterns in Python