勉強日記

チラ裏

GoF本 Memento

ねらい

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

AKA

  • Token

モチベーション

  • オブジェクトの内部状態を保存したいことがある
    • undo/redoのサポート等
  • 状態のgetter/setterを安易に公開するのはカプセル化をブチ壊す行為
    • 全publicと大して変わらない
  • カプセル化を守りつつ、内部状態を外部に書き出し、後で再度読み込みたい

つかいどころ

  • オブジェクトの内部状態(の一部)のスナップショットを保存する必要がある
    • あとでその状態に戻りたい
  • 内部状態について、直接的に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
    • GoFIteratorパターン
      • Iteratorに走査の責務をもたせ、「現在の要素」といった状態を保持させる
    • そうじゃない、Mementoベースのパターン
      • Aggregateに走査の責務をもたせる
      • 「走査の状態」としてMementoクラスを定義する
      • aggregate->next(iterationStatePtr);
        という感じに使用する
      • 「走査の状態」を外出しするので、同時に複数の走査を行うことができる