GoF本 Memento

GoFデザインパターン勉強メモ

ねらい

  • カプセル化を違反することなく、オブジェクトの内部状態を外部に書き出し、後で再度読み込む

AKA

  • Token

モチベーション

  • オブジェクトの内部状態を保存したいことがある

    • undo/redoのサポート等
  • 状態のgetter/setterを安易に公開するのはカプセル化をブチ壊す行為

    • 全publicと大して変わらない
  • カプセル化を守りつつ、内部状態を外部に書き出し、後で再度読み込みたい

つかいどころ

  • オブジェクトの内部状態(の一部)のスナップショットを保存する必要がある

    • あとでその状態に戻りたい

      • undo/redoとか
  • 内部状態について、直接的にgetter/setterを定義するとカプセル化をブチ壊してしまう

登場人物

  • Memento

    • originatorの内部状態を保持
    • 基本的にDTO

      • こいつ自身バリバリ仕事するクラスではない
  • Originator

    • mementoオブジェクトを生成し、内部状態のスナップショットをとる
    • mementoオブジェクトを読み込み、内部状態を復元する
  • Caretaker

    • mementoオブジェクトを保持する人
    • mementoの内容には触れない

      • getもsetもダメ

クライアントコードからの利用

  • caretakeroriginatormementoの生成をリクエストする
  • caretakermementoを保持する
  • originatorの内部状態を復元する必要がでてきたら、caretakermementooriginatorに渡す
  • originatorは、mementoから値を読み込み、内部状態を復元する
  • mementoは使われないこともある
  • mementoの後始末の責務はcaretakerにある

結果

  • カプセル化が守られる
  • Originatorがシンプルになる

    • Memento以外のパターンとしては、内部状態のバージョン管理等がある

      • 複雑
  • 高コストかも

    • originatorの内部状態が巨大な場合、そのcloningは時間・空間ともにコスト高い
    • 差分のみ保存することで回避できる
  • 狭い/広いインタフェースの定義

    • OriginatorからMementoの内容には書き込み・読み出しともに可能
    • 他クラスからMementoの内容には一切触れられない
  • 隠れたコスト(空間、管理)

    • Caretakerからはmementoの中身が見えないため、Caretakerのストレージコストが思ってたよりもかさみました、ということも
    • Originatorがnewして返したmementoは、Caretakerが責任をもってdeleteする

実装にあたり考えるべきこと

  • 言語のサポート

    • 狭い/広いインタフェースの定義には、C++ならfriendが使える

      • Originatorだけに限定で公開する、広いインタフェース

        • private/protected
        • OriginatorMementoとをfriendにする
      • Originator以外にも公開する、狭いインタフェース

        • public
  • 内部状態の差分を保存する

    • ストレージコストの削減

関連するパターン

  • Command

    • undoのサポートで併用
  • Iterator

    • GoFのIteratorパターン

      • Iteratorに走査の責務をもたせ、「現在の要素」といった状態を保持させる
    • そうじゃない、Mementoベースのパターン

      • Aggregateに走査の責務をもたせる
      • 「走査の状態」としてMementoクラスを定義する
      • aggregate->next(iterationStatePtr);
        という感じに使用する
      • 「走査の状態」を外出しするので、同時に複数の走査を行うことができる