GoF本 Visitor

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

ねらい

  • あるクラス群(Element派生)に対する、同じ目的の操作群を、1つのクラスにまとめる
  • 操作群の追加を容易にする

    • Elementとその派生に変更を生じさせない

モチベーション

  • 例) プログラムの解析・コード解析等
  • AST: Abstract Sytax Tree(抽象構文木)は、さまざまなNode派生クラスのオブジェクトからなる

    • AssinmentNode
      代入演算
    • VariableRefNode
      変数読み出し
  • ASTに対してさまざまな操作を行いたい

    • 型チェック
    • オブジェクトコード生成
    • pretty print
  • Visitorパターンを知らないと、こういった操作を、各Node派生に実装したくなる

    • AssignmentNode::TypeCheck()
    • VariableRefNode::TypeCheck()
  • そうすると、下記のようなデメリットが生じる

    • 同じ目的の操作が各Node派生に散らばり、理解しづらくなる
    • 操作を追加するたびに、Nodeに変更を加えなければならない

      • Nodeクラス利用側が影響を受ける
      • Nodeの全派生クラスが影響を受ける

        • 再コンパイルが必要
  • 同じ目的の操作を1つのクラスに切り出したい … Visitorクラス

つかいどころ

  • オブジェクト構造がさまざまなクラス(ConcreteElement)から構成されている

    • クラスごとに異なる操作を行いたい
  • 目的が同じ操作を、各ConcreteElementに散らかしたくない

    • AssignmentNode::TypeCheck()
    • VariableRefNode::TypeCheck()
  • ConcreteElementクラスが追加されることはあまりない

    • しょっちゅう追加されるならVisitorパターンは不向き

構造

20190103214416

登場人物

  • Visitor

    • ConcreteElementに対応するVisitメソッドの定義

      • 名前を分ける
      • 同じ名前でオーバーロード
  • ConcreteVisitor

    • 目的が同じ1つの操作ごとに派生をつくる

      • 【補】型チェック用Visitor派生の例

        • TypeCheckVisitor::VisitAssignmentNode(AssignmentNode node)
        • TypeCheckVisitor::VisitVariableRefNode(VariableRefNode node)
    • オブジェクト構造を走査する際、状態を蓄積できる

      • 【補】例) 変数の被参照箇所を数えるなど
  • Element

    • Accept(Visitor visitor)メソッドの定義
  • ConcreteElement

    • Acceptメソッドの実装

      • VisitorのどのVisitメソッドで操作されればよいかを知っている
      • 【補】オーバーロード実装なら知らなくてもいい
  • オブジェクト構造

    • elementオブジェクトをまとめた構造

      • Composite
      • 汎用コレクション

        • Array
        • List
        • etc.

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

  1. クライアントコードはconcreteVisitorを生成
  2. オブジェクト構造を走査するさい、element->Accept(concreteVisitor)する
  3. Element派生は、visitorを受け取ったら、しかるべきVisitメソッドを呼び出す

    • visitor->VisitXxx(this)
  4. 2.でelementによるシングルディスパッチ、3.でvisitorによるシングルディスパッチにより処理が決定される

    • 動的ポリモーフィズム
    • 正味、elementvisitorダブルディスパッチになる

結果

  • 新しい操作の追加が楽

    • ConcreteVisitorクラスを追加するだけ
    • Element側に変更は生じない
  • ConcreteVisitorに、目的が同じ関連する操作がひとまとめになる

    • 関連しないものは別のConcreteVisitorにまとまる
  • ConcreteElementの追加は大変になる

    • Visitorと全派生に、対応するVisitメソッドを追加しなければならない
    • ConcreteElementがしょっちゅう追加されるならVisitorパターンは不向き
  • Visitする対象は同じ継承ツリーにいる必要はない

    • 構造を参照
  • 状態を蓄積できる

    • 【補】コードの静的解析において、変数の被参照回数を数えるなど
  • カプセル化を壊してしまうことがある

    • ConcreteElementAは、全ConcreteVisitorVisitA(ConcreteElementA element)メソッドで用を満たせるインターフェースを持たねばならない
    • ConcreteElementAはpublicなgetterまみれになりカプセル化が台無しになることも

      • 【所感】friendにしてはどうか?

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

Visitをオーバーロードする

  • メリット

    • 引数は違えど、同じ目的の処理を行うということが強調される
  • デメリット

    • コードを追いづらい

ダブルディスパッチ

  • visitorの型とelementの型の2つによって処理が決定する
  • 言語が引数型によるディスパッチをサポートしているなら、Visitorパターン不要

    • Lisp
    • C#のdynamic

オブジェクト構造の走査は誰が行う?

  • オブジェクト構造自身

    • よくある実装
    • Compositeなら再帰的にAccept(visitor)を呼び出せばよい
  • Iterator

    • internal iterator
    • external iterator
  • Visitor

    • 走査処理をConcreteVisitorごとに書く必要あり
    • 走査のしかたが特殊な場合などは選択に値する

関連するパターン

  • Composite

    • 走査対象のオブジェクト構造はCompositeであったりする
  • Interpreter

    • ASTの例まさにそのもの

英語

  • consolidate

    • 一元管理する

      • 責務を中央集権にする場合などに