Python メメントパターン(Memento)

メメントパターンは、カプセル化を壊すことなく、オブジェクトの内部状態を取得・保存し、必要に応じて以前の状態に復元できるようにする、行動デザインパターンです。

あなたがゲームをプレイしていて、ゲームに「セーブ」機能があると想像してください。いつでも現在のゲーム進行を保存でき、後でゲームに失敗した場合や、あるレベルをやり直したい場合に、以前のセーブデータを読み込むことができます。メメントパターンは、この「セーブ・ロード」機能を実現するプログラミングソリューションです。


なぜメメントパターンが必要なのか?

適用シーン

  • テキストエディタ:元に戻す(Undo)とやり直す(Redo)機能を実装する
  • ゲーム開発:ゲームの進行状況と状態を保存する
  • グラフィックソフトウェア:操作履歴をサポートする
  • トランザクション処理:操作失敗時に以前の状態へロールバックする

解決する問題

  • オブジェクト状態の保存が必要だが、オブジェクト内部の詳細を公開したくない
  • 状態復元メカニズムを提供し、「後悔の薬」機能をサポートする
  • オブジェクトのカプセル化を維持し、状態保存ロジックとビジネスロジックが混在するのを避ける

メメントパターンの核心コンポーネント

メメントパターンには3つの主要な役割があります:

1. 発起人(Originator)

  • 状態を保存する必要があるオブジェクト
  • 現在の状態を記録するメメントを作成する
  • メメントを使用して以前の状態を復元する

2. メメント(Memento)

  • 発起人オブジェクトの内部状態を保存する
  • 発起人以外のオブジェクトがメメントの内容にアクセスするのを防ぐ

3. 管理者(Caretaker)

  • メメントの保存を担当する
  • メメントの内容を操作したり検査したりしてはならない


Pythonでメメントパターンを実装する

テキストエディタの例を通して、メメントパターンを具体的に実装しましょう。

基本実装

例

class TextEditorMemento:
    """メメントクラス - テキストエディタの状態を保存する"""
   
    def __init__(self, content):
        self._content = content
   
    def get_content(self):
        """保存された内容を取得する"""
        return self._content


class TextEditor:
    """発起人クラス - テキストエディタ"""
   
    def __init__(self):
        self._content = ""
   
    def write(self, text):
        """テキストを書き込む"""
        self._content += text
   
    def get_content(self):
        """現在の内容を取得する"""
        return self._content
   
    def save(self):
        """メメントを作成 - 現在の状態を保存する"""
        return TextEditorMemento(self._content)
   
    def restore(self, memento):
        """状態を復元 - メメントから読み取る"""
        self._content = memento.get_content()


class HistoryManager:
    """管理者クラス - 履歴管理マネージャー"""
   
    def __init__(self):
        self._mementos = []
   
    def push(self, memento):
        """メメントを保存する"""
        self._mementos.append(memento)
   
    def pop(self):
        """最新のメメントを取得する"""
        if self._mementos:
            return self._mementos.pop()
        return None

完全なサンプルコード

例

# メメントパターンの完全な例
def memo_pattern_demo():
    # テキストエディタと履歴マネージャーを作成
    editor = TextEditor()
    history = HistoryManager()
   
    print("=== テキストエディタ メメントパターン デモ ===\n")
   
    # 最初の編集と保存
    editor.write("Hello, ")
    history.push(editor.save())
    print(f現在の内容: {editor.get_content()})
    print("✅ 状態を保存しました\n")
   
    # 2回目の編集と保存
    editor.write("World!")
    history.push(editor.save())
    print(f現在の内容: {editor.get_content()})
    print("✅ 状態を保存しました\n")
   
    # 3回目の編集だが保存しない
    editor.write(" This is new text.")
    print(f現在の内容: {editor.get_content()})
    print("❌ 現在の編集は保存されていません\n")
   
    # 元に戻す操作 - 前回保存した状態に回復
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"元に戻した後の内容: {editor.get_content()}")
        print("↩️ 前の保存ポイントに戻しました\n")
   
    # さらに元に戻す
    memento = history.pop()
    if memento:
        editor.restore(memento)
        print(f"さらに元に戻した後の内容: {editor.get_content()}")
        print("↩️ 最初の状態に戻しました")


# デモを実行
if __name__ == "__main__":
    memo_pattern_demo()

実行結果:


高度な応用:複数の状態をサポートするメメント

実際のアプリケーションでは、オブジェクトの複数の属性を保存する必要がある場合があります。次はより複雑な例です:

例

import datetime
import json

class GameStateMemento:
    """ゲーム状態メメント"""
   
    def __init__(self, level, score, player_health, inventory):
        self._state = {
            'level': level,
            'score': score,
            'player_health': player_health,
            'inventory': inventory.copy(),  # 参照問題を避けるためにコピーを作成
            'timestamp': datetime.datetime.now().isoformat()
        }
   
    def get_state(self):
        """完全な状態を取得する"""
        return self._state.copy()
   
    def get_timestamp(self):
        """保存時刻を取得する"""
        return self._state['timestamp']


class Game:
    """ゲームクラス - 発起人"""
   
    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):
        """ゲーム進行をシミュレートする"""
        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)
       
        # ヒットポイントは負にできない
        self.player_health = max(0, self.player_health)
   
    def display_status(self):
        """現在の状態を表示する"""
        status = f"""
🎮 ゲーム状態:
レベル: {self.level}
スコア: {self.score}
ヒットポイント: {self.player_health}
所持品: {', '.join(self.inventory)}
        """

        print(status)
   
    def save_game(self):
        """ゲームの状態を保存する"""
        return GameStateMemento(
            self.level,
            self.score,
            self.player_health,
            self.inventory
        )
   
    def load_game(self, memento):
        """ゲームの状態を読み込む"""
        state = memento.get_state()
        self.level = state['level']
        self.score = state['score']
        self.player_health = state['player_health']
        self.inventory = state['inventory']


class SaveManager:
    """セーブデータマネージャー"""
   
    def __init__(self):
        self.saves = {}
   
    def create_save(self, save_name, memento):
        """セーブデータを作成する"""
        self.saves[save_name] = memento
        print(f"💾 セーブデータ '{save_name}' の作成に成功しました!")
   
    def load_save(self, save_name):
        """セーブデータを読み込む"""
        if save_name in self.saves:
            print(f"🔄 セーブデータ '{save_name}' を読み込んでいます...")
            return self.saves[save_name]
        else:
            print(f❌ セーブデータ '{save_name}' は存在しません!)
            return None
   
    def list_saves(self):
        """すべてのセーブデータを一覧表示する"""
        if not self.saves:
            print("📁 セーブデータはまだありません")
            return
       
        print("\n📋 セーブデータ一覧:")
        for name, memento in self.saves.items():
            timestamp = memento.get_timestamp()
            print(f" - {name} (保存時刻: {timestamp})")


# 高度な例のデモ
def advanced_memo_demo():
    print("=== ゲームセーブデータシステムのデモ ==="\n")
   
    game = Game()
    save_manager = SaveManager()
   
    # 初期状態
    print("🎯 新しいゲームを開始:")
    game.display_status()
   
    # ゲーム進行
    print("🚀 ゲームをプレイ中...")
    game.play(level_increase=2, score_increase=500, new_item='shield')
    game.display_status()
   
    # 最初のセーブデータを保存
    save_manager.create_save("第1ステージクリア", game.save_game())
   
    # ゲームを続ける
    print("⚔️ 冒険を続ける...")
    game.play(level_increase=1, score_increase=200, health_change=-30, new_item='magic_wand')
    game.display_status()
   
    # 2番目のセーブデータを保存
    save_manager.create_save("第2ステージ開始", game.save_game())
   
    # セーブデータ一覧を表示
    save_manager.list_saves()
   
    # 最初のセーブデータを読み込む
    print("\n⏪ 第1ステージクリア状態に戻る:")
    first_save = save_manager.load_save("第1ステージクリア")
    if first_save:
        game.load_game(first_save)
        game.display_status()


# 高度な例を実行
if __name__ == "__main__":
    advanced_memo_demo()

メメントパターンの長所と短所

長所

  1. カプセル化の保護:オブジェクトの内部状態を公開せず、カプセル化を維持する
  2. 発起人の簡素化:状態保存ロジックをビジネスロジックから分離する
  3. 回復を実装しやすい:シンプルな状態復元メカニズムを提供する
  4. 複数回の元に戻す操作をサポート:複数の時点の状態を保存できる

短所

  1. メモリ消費:状態が大きいか保存頻度が高いと、大量のメモリを消費します
  2. 管理者のオーバーヘッド:メメントを管理するための追加クラスが必要
  3. ライフサイクル管理:メメントの作成と破棄を適切に処理する必要がある

ベストプラクティスと注意事項

1. メモリ最適化戦略

例

# 保存する履歴の数を制限する
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:
            # 最も古いレコードを削除する
            self._mementos.pop(0)
        self._mementos.append(memento)

2. 複雑なオブジェクト状態の処理

複雑な参照を含むオブジェクトの場合、メメントが深いコピーを作成するようにする必要があります:

例

import copy

class ComplexMemento:
    def __init__(self, complex_state):
        # 深いコピーを使用して参照の問題を回避する
        self._state = copy.deepcopy(complex_state)

3. 選択的保存

すべての状態を保存する必要はなく、重要な属性だけを保存するように選択できます:

例

def create_selective_memento(self):
    """重要な状態を選択的に保存する"""
    important_state = {
        'essential_data': self.essential_data,
        'critical_settings': self.critical_settings
        # 一時データとキャッシュを無視する
    }
    return SelectiveMemento(important_state)

実際の適用シーン

1. グラフィックエディタ

例

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

2. 設定管理マネージャー

例

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

3. トランザクション操作

例

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

まとめ

メメントパターンは強力で実用的なデザインパターンであり、優雅な状態保存・復元メカニズムを提供します。状態保存ロジックをビジネスロジックから分離することで、コードの整潔さを保つだけでなく、システムの保守性も高めます。

重要なポイント:

  • メメントパターンの核心は「保存・復元」メカニズムです
  • 3つのコア役割がそれぞれの責務を果たし、責任が明確に分かれています
  • 実際の使用では、メモリ管理と状態選択に注意する必要があります
  • 元に戻す、やり直す、または状態ロールバックが必要なシナリオに適用できます
その他の拡張