勉強日記

チラ裏

GoF本 Visitor

ねらい

  • あるクラス群(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パターンは不向き

構造

f:id:wand_ta:20190103214416p:plain

登場人物

  • 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によるシングルディスパッチにより処理が決定される

結果

  • 新しい操作の追加が楽
    • ConcreteVisitorクラスを追加するだけ
    • Element側に変更は生じない
  • ConcreteVisitorに、目的が同じ関連する操作がひとまとめになる
    • 関連しないものは別のConcreteVisitorにまとまる
  • ConcreteElementの追加は大変になる
    • Visitorと全派生に、対応するVisitメソッドを追加しなければならない
    • ConcreteElementがしょっちゅう追加されるならVisitorパターンは不向き
  • Visitする対象は同じ継承ツリーにいる必要はない
    • 構造を参照
  • 状態を蓄積できる
    • 【補】コードの静的解析において、変数の被参照回数を数えるなど
  • カプセル化を壊してしまうことがある
    • ConcreteElementAは、全ConcreteVisitorVisitA(ConcreteElementA element)メソッドで用を満たせるインターフェースを持たねばならない
    • ConcreteElementAはpublicなgetterまみれになりカプセル化が台無しになることも
      • 【所感】friendにしてはどうか?

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

Visitをオーバーロードする

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

ダブルディスパッチ

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

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

  • オブジェクト構造自身
    • よくある実装
    • Compositeなら再帰的にAccept(visitor)を呼び出せばよい
  • Iterator
  • Visitor
    • 走査処理をConcreteVisitorごとに書く必要あり
    • 走査のしかたが特殊な場合などは選択に値する

関連するパターン

  • Composite
    • 走査対象のオブジェクト構造はCompositeであったりする
  • Interpreter
    • ASTの例まさにそのもの

英語

  • consolidate
    • 一元管理する
      • 責務を中央集権にする場合などに