The Memento pattern is a behavioral design pattern that provides the ability to restore an object to its previous state without violating encapsulation principles. This pattern is particularly useful when you need to implement undo functionality, checkpoints, or snapshots in your application.
Core Concept
The Memento pattern captures and externalizes an object’s internal state so that the object can be restored to this state later. The key insight is that only the originator object should have access to the memento’s contents, preserving encapsulation while enabling state restoration.
Pattern Structure
┌─────────────┐ creates ┌─────────────┐
│ │──────────────→│ │
│ Originator │ │ Memento │
│ │←──────────────│ │
└─────────────┘ restores └─────────────┘
△ │
│ │
│ manages │ stores
│ │
▽ ▽
┌─────────────┐ ┌─────────────┐
│ │──────────────→│ │
│ Caretaker │ stores │ State │
│ │ │ Information │
└─────────────┘ └─────────────┘
Key Components
Originator: The object whose state needs to be saved and restored. It creates mementos containing snapshots of its current state and uses mementos to restore its previous state.
Memento: A value object that acts as a snapshot of the originator’s state. It should be immutable and only accessible to the originator that created it.
Caretaker: Manages mementos but never operates on or examines their contents. It’s responsible for storing mementos and providing them back to the originator when restoration is needed.
When to Use
The Memento pattern is ideal when you need to:
- Implement undo/redo functionality in applications
- Create snapshots or checkpoints of object states
- Provide rollback capabilities in transactional systems
- Save and restore game states
- Implement version control systems for objects
Benefits and Trade-offs
Benefits:
- Preserves encapsulation boundaries
- Simplifies the originator by delegating state management
- Provides clean separation of concerns
Trade-offs:
- Can be memory-intensive if mementos are large or numerous
- Caretaker lifecycle management becomes important
- May impact performance with frequent state captures
Example 1: Text Editor with Undo Functionality
This example demonstrates the Memento pattern through a text editor that supports undo and redo operations. The TextEditor
class acts as the originator, creating snapshots of its state including text content and cursor position. The TextMemento
is implemented as an immutable dataclass that captures the editor’s state. The EditorHistory
class serves as the caretaker, managing a collection of mementos and providing undo/redo functionality.
from typing import List, Optional
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True)
class TextMemento:
"""
Memento class that stores the state of a text editor.
Frozen dataclass ensures immutability.
"""
content: str
cursor_position: int
timestamp: datetime
class TextEditor:
"""
Originator class that maintains text content and can create/restore mementos.
"""
def __init__(self) -> None:
self._content: str = ""
self._cursor_position: int = 0
def write(self, text: str) -> None:
"""Insert text at current cursor position."""
self._content = (self._content[:self._cursor_position] +
text +
self._content[self._cursor_position:])
self._cursor_position += len(text)
def delete(self, length: int) -> None:
"""Delete specified number of characters before cursor."""
start_pos = max(0, self._cursor_position - length)
self._content = (self._content[:start_pos] +
self._content[self._cursor_position:])
self._cursor_position = start_pos
def set_cursor_position(self, position: int) -> None:
"""Move cursor to specified position."""
self._cursor_position = max(0, min(position, len(self._content)))
def create_memento(self) -> TextMemento:
"""Create a snapshot of current editor state."""
return TextMemento(
content=self._content,
cursor_position=self._cursor_position,
timestamp=datetime.now()
)
def restore_from_memento(self, memento: TextMemento) -> None:
"""Restore editor state from a memento."""
self._content = memento.content
self._cursor_position = memento.cursor_position
def get_content(self) -> str:
"""Get current text content."""
return self._content
def get_cursor_position(self) -> int:
"""Get current cursor position."""
return self._cursor_position
def display(self) -> str:
"""Display content with cursor position marked."""
content_with_cursor = (self._content[:self._cursor_position] +
"|" +
self._content[self._cursor_position:])
return content_with_cursor
class EditorHistory:
"""
Caretaker class that manages editor state history for undo functionality.
"""
def __init__(self, editor: TextEditor) -> None:
self._editor = editor
self._history: List[TextMemento] = []
self._current_index: int = -1
def save_state(self) -> None:
"""Save current editor state to history."""
# Remove any future states if we're not at the end
self._history = self._history[:self._current_index + 1]
# Add new state
memento = self._editor.create_memento()
self._history.append(memento)
self._current_index += 1
# Optional: Limit history size to prevent memory issues
max_history_size = 50
if len(self._history) > max_history_size:
self._history.pop(0)
self._current_index -= 1
def undo(self) -> bool:
"""Undo last operation by restoring previous state."""
if self._current_index > 0:
self._current_index -= 1
memento = self._history[self._current_index]
self._editor.restore_from_memento(memento)
return True
return False
def redo(self) -> bool:
"""Redo next operation by restoring next state."""
if self._current_index < len(self._history) - 1:
self._current_index += 1
memento = self._history[self._current_index]
self._editor.restore_from_memento(memento)
return True
return False
def can_undo(self) -> bool:
"""Check if undo operation is possible."""
return self._current_index > 0
def can_redo(self) -> bool:
"""Check if redo operation is possible."""
return self._current_index < len(self._history) - 1
def get_history_info(self) -> str:
"""Get formatted history information for debugging."""
info = f"History size: {len(self._history)}\n"
info += f"Current index: {self._current_index}\n"
info += f"Can undo: {self.can_undo()}\n"
info += f"Can redo: {self.can_redo()}\n"
return info
# Usage demonstration
if __name__ == "__main__":
# Create text editor and history manager
editor = TextEditor()
history = EditorHistory(editor)
# Initial state
history.save_state()
print("Initial state:", editor.display())
# Make some changes
editor.write("Hello")
history.save_state()
print("After writing 'Hello':", editor.display())
editor.write(" World")
history.save_state()
print("After writing ' World':", editor.display())
editor.set_cursor_position(5) # Move cursor between "Hello" and " World"
editor.write(" Python")
history.save_state()
print("After inserting ' Python':", editor.display())
# Demonstrate undo functionality
print("\n--- Undo Operations ---")
while history.can_undo():
history.undo()
print("After undo:", editor.display())
# Demonstrate redo functionality
print("\n--- Redo Operations ---")
while history.can_redo():
history.redo()
print("After redo:", editor.display())
Key aspects of this implementation:
- Encapsulation: The memento only exposes necessary data and remains immutable
- State Management: The caretaker handles complex history navigation logic
- Memory Management: History size is limited to prevent excessive memory usage
- User Experience: Provides intuitive undo/redo operations familiar to users
Example 2: Game State Management System
This example showcases the Memento pattern in a game context where state preservation is crucial for features like checkpoints, save/load functionality, and recovery from failure states. The Game
class maintains complex state including player progress, inventory, enemy positions, and game status. The GameMemento
captures comprehensive snapshots while the GameStateManager
provides sophisticated state management capabilities.
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from enum import Enum
import json
class GameStatus(Enum):
"""Enumeration of possible game states."""
PLAYING = "playing"
PAUSED = "paused"
GAME_OVER = "game_over"
@dataclass(frozen=True)
class GameMemento:
"""
Memento storing complete game state snapshot.
Includes all necessary data to restore game state.
"""
level: int
score: int
lives: int
player_position: Tuple[int, int]
enemies: List[Tuple[int, int]]
items: Dict[str, int] # item_type -> quantity
game_status: GameStatus
timestamp: float
class Game:
"""
Originator class representing a game that can save and restore its state.
"""
def __init__(self) -> None:
# Game state variables
self._level: int = 1
self._score: int = 0
self._lives: int = 3
self._player_position: Tuple[int, int] = (0, 0)
self._enemies: List[Tuple[int, int]] = []
self._items: Dict[str, int] = {"health_potion": 0, "key": 0, "coin": 0}
self._game_status: GameStatus = GameStatus.PLAYING
# Game action methods
def move_player(self, x: int, y: int) -> None:
"""Move player to new position."""
self._player_position = (x, y)
def add_score(self, points: int) -> None:
"""Add points to current score."""
self._score += points
def lose_life(self) -> None:
"""Decrease lives by 1."""
self._lives = max(0, self._lives - 1)
if self._lives == 0:
self._game_status = GameStatus.GAME_OVER
def gain_life(self) -> None:
"""Increase lives by 1."""
self._lives += 1
def level_up(self) -> None:
"""Advance to next level."""
self._level += 1
# Reset enemies for new level
self._enemies = self._generate_enemies()
def add_item(self, item_type: str, quantity: int = 1) -> None:
"""Add items to inventory."""
if item_type in self._items:
self._items[item_type] += quantity
else:
self._items[item_type] = quantity
def use_item(self, item_type: str, quantity: int = 1) -> bool:
"""Use items from inventory. Returns True if successful."""
if item_type in self._items and self._items[item_type] >= quantity:
self._items[item_type] -= quantity
return True
return False
def add_enemy(self, position: Tuple[int, int]) -> None:
"""Add enemy at specified position."""
self._enemies.append(position)
def remove_enemy(self, position: Tuple[int, int]) -> bool:
"""Remove enemy at specified position."""
try:
self._enemies.remove(position)
return True
except ValueError:
return False
def pause_game(self) -> None:
"""Pause the game."""
if self._game_status == GameStatus.PLAYING:
self._game_status = GameStatus.PAUSED
def resume_game(self) -> None:
"""Resume the game."""
if self._game_status == GameStatus.PAUSED:
self._game_status = GameStatus.PLAYING
def _generate_enemies(self) -> List[Tuple[int, int]]:
"""Generate enemies for current level."""
# Simple enemy generation based on level
enemies = []
for i in range(self._level * 2): # More enemies each level
enemies.append((i * 10, i * 5))
return enemies
# Memento pattern implementation
def create_memento(self) -> GameMemento:
"""Create snapshot of current game state."""
import time
return GameMemento(
level=self._level,
score=self._score,
lives=self._lives,
player_position=self._player_position,
enemies=self._enemies.copy(), # Create copy to avoid reference issues
items=self._items.copy(),
game_status=self._game_status,
timestamp=time.time()
)
def restore_from_memento(self, memento: GameMemento) -> None:
"""Restore game state from memento."""
self._level = memento.level
self._score = memento.score
self._lives = memento.lives
self._player_position = memento.player_position
self._enemies = list(memento.enemies) # Create new list
self._items = dict(memento.items) # Create new dict
self._game_status = memento.game_status
# Getter methods for displaying state
def get_game_info(self) -> str:
"""Get formatted game information."""
info = f"""
Game Status: {self._game_status.value.title()}
Level: {self._level}
Score: {self._score}
Lives: {self._lives}
Player Position: {self._player_position}
Enemies: {len(self._enemies)} enemies
Items: {', '.join(f'{k}: {v}' for k, v in self._items.items())}
""".strip()
return info
class GameStateManager:
"""
Caretaker class managing game state checkpoints and save/load functionality.
"""
def __init__(self, game: Game) -> None:
self._game = game
self._checkpoints: List[GameMemento] = []
self._saved_games: Dict[str, GameMemento] = {}
def create_checkpoint(self) -> None:
"""Create a checkpoint of current game state."""
checkpoint = self._game.create_memento()
self._checkpoints.append(checkpoint)
# Limit checkpoint history to prevent memory issues
max_checkpoints = 10
if len(self._checkpoints) > max_checkpoints:
self._checkpoints.pop(0)
print(f"Checkpoint created at level {checkpoint.level}")
def restore_last_checkpoint(self) -> bool:
"""Restore game to most recent checkpoint."""
if self._checkpoints:
latest_checkpoint = self._checkpoints[-1]
self._game.restore_from_memento(latest_checkpoint)
print(f"Restored to checkpoint at level {latest_checkpoint.level}")
return True
print("No checkpoints available")
return False
def save_game(self, save_name: str) -> None:
"""Save current game state with a name."""
memento = self._game.create_memento()
self._saved_games[save_name] = memento
print(f"Game saved as '{save_name}' at level {memento.level}")
def load_game(self, save_name: str) -> bool:
"""Load game state by name."""
if save_name in self._saved_games:
memento = self._saved_games[save_name]
self._game.restore_from_memento(memento)
print(f"Game '{save_name}' loaded (level {memento.level})")
return True
print(f"No saved game found with name '{save_name}'")
return False
def list_saves(self) -> None:
"""Display all saved games."""
if not self._saved_games:
print("No saved games")
return
print("Saved Games:")
print("-" * 40)
for save_name, memento in self._saved_games.items():
print(f"{save_name:15} | Level {memento.level:2} | Score {memento.score:6}")
def get_checkpoint_info(self) -> str:
"""Get information about available checkpoints."""
if not self._checkpoints:
return "No checkpoints available"
info = f"Checkpoints available: {len(self._checkpoints)}\n"
info += "Recent checkpoints:\n"
for i, checkpoint in enumerate(self._checkpoints[-3:], 1): # Show last 3
info += f" {i}. Level {checkpoint.level}, Score {checkpoint.score}\n"
return info.rstrip()
# Usage demonstration
if __name__ == "__main__":
# Create game and state manager
game = Game()
state_manager = GameStateManager(game)
print("=== Game State Management Demo ===\n")
# Initial game state
print("Initial game state:")
print(game.get_game_info())
# Play some game actions
print("\n--- Playing the game ---")
game.move_player(10, 20)
game.add_score(100)
game.add_item("coin", 5)
game.add_item("health_potion", 2)
game.add_enemy((50, 30))
# Create checkpoint
state_manager.create_checkpoint()
print("\nAfter some gameplay:")
print(game.get_game_info())
# Continue playing
print("\n--- More gameplay ---")
game.level_up()
game.add_score(250)
game.move_player(100, 150)
game.lose_life()
# Save game
state_manager.save_game("before_boss_fight")
print("\nAfter leveling up:")
print(game.get_game_info())
# Simulate difficult section
print("\n--- Difficult section ---")
game.lose_life()
game.lose_life() # Game over
print("\nAfter losing lives:")
print(game.get_game_info())
# Restore from checkpoint
print("\n--- Restoring from checkpoint ---")
state_manager.restore_last_checkpoint()
print("After checkpoint restore:")
print(game.get_game_info())
# Show save management
print("\n--- Save Management ---")
state_manager.save_game("good_progress")
state_manager.save_game("backup_save")
state_manager.list_saves()
print("\n" + state_manager.get_checkpoint_info())
Notable features of this implementation:
- Complex State Capture: Handles multiple data types including collections and enums
- Multiple Restoration Points: Supports both temporary checkpoints and permanent saves
- Memory Management: Limits checkpoint history to prevent resource exhaustion
- User-Friendly Interface: Provides named save slots and information about available saves
- State Integrity: Creates defensive copies to prevent unintended state sharing
The Memento pattern proves invaluable in scenarios requiring reliable state management, offering a clean separation between state capture, storage, and restoration while maintaining proper encapsulation boundaries.