Python Memento Pattern (Memento)

The Memento Pattern is a behavioral design pattern that allows capturing and saving an object's internal state without breaking encapsulation, so that it can be restored to a previous state when needed.

Imagine you are playing a game that provides a "save" feature. You can save the current game progress at any time. If you fail later or want to retry a level, you can load a previous save. The Memento Pattern is a programming solution that implements this "save-load" functionality.


Why do we need the Memento Pattern?

Application Scenarios

  • Text editor: Implement undo and redo functionality
  • Game development: Save game progress and state
  • Graphics software: Support operation history
  • Transaction processing: Roll back to a previous state when an operation fails

Problems Solved

  • Need to save object state without exposing internal details
  • Provide a state recovery mechanism, supporting a "regret medicine" function (i.e., undo ability)
  • Maintain object encapsulation and avoid mixing state-saving logic with business logic

Core Components of the Memento Pattern

The Memento Pattern consists of three main roles:

1. Originator

  • The object that needs to save state
  • Creates a memento to record the current state
  • Uses the memento to restore a previous state

2. Memento

  • Stores the internal state of the originator object
  • Prevents objects other than the originator from accessing the memento's contents

3. Caretaker

  • Responsible for saving mementos
  • Cannot operate on or inspect the contents of mementos


Python Implementation of the Memento Pattern

Let's implement the Memento Pattern concretely through a text editor example.

Basic Implementation

Example

class TextEditorMemento:
    """Memento class - saves the state of the text editor"""
   
    def __init__(self, content):
        self._content = content
   
    def get_content(self):
        """Get the saved content"""
        return self._content


class TextEditor:
    """Originator class - Text editor"""
   
    def __init__(self):
        self._content = ""
   
    def write(self, text):
        """Write text"""
        self._content += text
   
    def get_content(self):
        """Get current content"""
        return self._content
   
    def save(self):
        """Create memento - save current state"""
        return TextEditorMemento(self._content)
   
    def restore(self, memento):
        """Restore state - read from memento"""
        self._content = memento.get_content()


class HistoryManager:
    """Caretaker class - History manager"""
   
    def __init__(self):
        self._mementos = []
   
    def push(self, memento):
        """Save memento"""
        self._mementos.append(memento)
   
    def pop(self):
        """Get the latest memento"""
        if self._mementos:
            return self._mementos.pop()
        return None

Complete Example Code

Example

# Complete example of the Memento Pattern
def memo_pattern_demo():
    # Create text editor and history manager
    editor = TextEditor()
    history = HistoryManager()
   
    print("=== Text Editor Memento Pattern Demo ==="\n")
   
    # First edit and save
    editor.write("Hello, ")
    history.push(editor.save())
    print(f"Current content: {editor.get_content()}")
    print("✅ State saved\n")
   
    # Second edit and save
    editor.write("World!")
    history.push(editor.save())
    print(f"Current content: {editor.get_content()}")
    print("✅ State saved\n")
   
    # Third edit but not save
    editor.write(" This is new text.")
    print(f"Current content: {editor.get_content()}")
    print("❌ Current edit not saved\n")
   
    # Undo operation - restore to the last saved state
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"Content after undo: {editor.get_content()}")
        print("↩️ Undone to the previous save point\n")
   
    # Undo again
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"Content after undo again: {editor.get_content()}")
        print("↩️ Undone to the initial state")


# Run the demo
if __name__ == "__main__":
    memo_pattern_demo()

Running result:


Advanced Application: Memento Supporting Multiple States

In real applications, we may need to save multiple attributes of an object. Here is a more complex example:

Example

import datetime
import json

class GameStateMemento:
    """Game state memento"""
   
    def __init__(self, level, score, player_health, inventory):
        self._state = {
            'level': level,
            'score': score,
            'player_health': player_health,
            'inventory': inventory.copy(),  # Create a copy to avoid reference issues
            'timestamp': datetime.datetime.now().isoformat()
        }
   
    def get_state(self):
        """Get full state"""
        return self._state.copy()
   
    def get_timestamp(self):
        """Get save time"""
        return self._state['timestamp']


class Game:
    """Game class - Originator"""
   
    def __init__(self):
        self.level = 1
        self.score = 0
        self.player_health = 100
        self.inventory = ['sword', 'potion']
   
    def play(self, level_increase=1, score_increase=100, health_change=0, new_item=None):
        """Simulate game process"""
        self.level += level_increase
        self.score += score_increase
        self.player_health += health_change
       
        if new_item and new_item not in self.inventory:
            self.inventory.append(new_item)
       
        # Health cannot be negative
        self.player_health = max(0, self.player_health)
   
    def display_status(self):
        """Display current state"""
        status = f"""
🎮 Game state:
Level: {self.level}
Score: {self.score}
Health: {self.player_health}
Inventory: {', '.join(self.inventory)}
        """

        print(status)
   
    def save_game(self):
        """Save game state"""
        return GameStateMemento(
            self.level,
            self.score,
            self.player_health,
            self.inventory
        )
   
    def load_game(self, memento):
        """Load game state"""
        state = memento.get_state()
        self.level = state['level']
        self.score = state['score']
        self.player_health = state['player_health']
        self.inventory = state['inventory']


class SaveManager:
    """Save manager"""
   
    def __init__(self):
        self.saves = {}
   
    def create_save(self, save_name, memento):
        """Create save"""
        self.saves[save_name] = memento
        print(f"💾 Save '{save_name}' created successfully!")
   
    def load_save(self, save_name):
        """Load save"""
        if save_name in self.saves:
            print(f"🔄 Loading save '{save_name}'...")
            return self.saves[save_name]
        else:
            print(f"❌ Save '{save_name}' does not exist!")
            return None
   
    def list_saves(self):
        """List all saves"""
        if not self.saves:
            print("📁 No saves available")
            return
       
        print("\n📋 Save list:")
        for name, memento in self.saves.items():
            timestamp = memento.get_timestamp()
            print(f" - {name} (Saved at: {timestamp})")


# Advanced example demo
def advanced_memo_demo():
    print("=== Game Save System Demo ==="\n")
   
    game = Game()
    save_manager = SaveManager()
   
    # Initial state
    print("🎯 Starting a new game:")
    game.display_status()
   
    # Game progress
    print("🚀 Playing the game...")
    game.play(level_increase=2, score_increase=500, new_item='shield')
    game.display_status()
   
    # Save the first save
    save_manager.create_save("Level 1 completed", game.save_game())
   
    # Continue the game
    print("⚔️ Continue adventuring...")
    game.play(level_increase=1, score_increase=200, health_change=-30, new_item='magic_wand')
    game.display_status()
   
    # Save the second save
    save_manager.create_save("Level 2 starts", game.save_game())
   
    # Display save list
    save_manager.list_saves()
   
    # Load the first save
    print("\n⏪ Return to Level 1 completed state:")
    first_save = save_manager.load_save("Level 1 completed")
    if first_save:
        game.load_game(first_save)
        game.display_status()


# Run the advanced example
if __name__ == "__main__":
    advanced_memo_demo()

Advantages and Disadvantages of the Memento Pattern

Advantages

  1. Encapsulation protection: Does not expose an object's internal state, maintaining encapsulation
  2. Simplify the originator: Separates state-saving logic from business logic
  3. Easy to implement recovery: Provides a simple state restoration mechanism
  4. Support multiple undos: Can save states at multiple points in time

Disadvantages

  1. Memory consumption: If the state is large or saves are frequent, it consumes a lot of memory
  2. Caretaker overhead: Requires an additional class to manage mementos
  3. Lifecycle management: Need to properly handle the creation and destruction of mementos

Best Practices and Precautions

1. Memory Optimization Strategy

Example

# Limit the number of history records saved
class LimitedHistoryManager:
    def __init__(self, max_size=10):
        self._mementos = []
        self._max_size = max_size
   
    def push(self, memento):
        if len(self._mementos) >= self._max_size:
            # Remove the oldest record
            self._mementos.pop(0)
        self._mementos.append(memento)

2. Handling Complex Object States

For objects with complex references, ensure the memento creates a deep copy:

Example

import copy

class ComplexMemento:
    def __init__(self, complex_state):
        # Use deep copy to avoid reference issues
        self._state = copy.deepcopy(complex_state)

3. Selective Saving

Not all states need to be saved; you can choose to save only important properties:

Example

def create_selective_memento(self):
    """Selectively save important state"""
    important_state = {
        'essential_data': self.essential_data,
        'critical_settings': self.critical_settings
        # Ignore temporary data and cache
    }
    return SelectiveMemento(important_state)

Real-World Application Scenarios

1. Graphic Editor

Example

class GraphicEditor:
    def save_state(self):
        return GraphicMemento(
            self.selected_shapes.copy(),
            self.canvas_state,
            self.view_settings
        )

2. Configuration Manager

Example

class ConfigManager:
    def backup_config(self):
        return ConfigMemento(
            self.current_settings.copy(),
            self.user_preferences
        )

3. Transaction Operations

Example

class DatabaseTransaction:
    def create_checkpoint(self):
        return TransactionMemento(
            self.connection_state,
            self.pending_operations.copy()
        )

Summary

The Memento pattern is a powerful and practical design pattern that provides an elegant mechanism for state saving and restoration. By separating state-saving logic from business logic, it not only keeps the code clean but also enhances system maintainability.

Key Points:

  • The core of the Memento pattern is the "save-restore" mechanism.
  • The three core roles each perform their own duties with clear responsibilities.
  • In practice, pay attention to memory management and state selection.
  • Suitable for scenarios requiring undo, redo, or state rollback.
Other Extensions