Home: Up: Back

Visitor

異なるクラスのオブジェクトに対する同種の操作を切り出して1クラスにまとめる。
同種の操作を集めたオブジェクトを入れ換えることで、元のクラス集合を変更せずに新しい操作が追加できる。


Visiteeを関連性の無い各Nodeに実装しておき、それらの種類ごとにそれら自身を操作対象とする操作インターフェイスをVisitor interfaceに定義します。

// 元のクラス集合に予め実装させておく
interface Visitee {
public void accept(Visitor visitor);
} // その後あまり変更のないクラス群
class Node1 implements Visitee {
public void accept(Visitor visitor) {
visitor.visit(this);
}
} // Visitor実装クラスを切り替えることで別の操作を行う
interface Visitor {
public void visit(Node1 node);
public void visit(Node2 node);
:
}

新たなVisitor実装クラスを用意して、各Visiteeのacceptに順次渡すことで、Visitee実装クラスごとに異なる実装での、各Visiteeに対する新たな操作を付け加えたことになります。

ここでメソッドオーバーロードによってVisitor#visit()を定義した場合、Visitee#accept()の実装は上に示したもので固定となります。
従って、Nodeの上にその実装を書いたAbstractNodeを用意すればaccept()メソッドは一つでよいように見えます。
abstract class AbstractNode implements Visitee {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Node1 extends AbstractNode { : }
しかし、これをもとにNode1やNode2などを定義するように変更すると、見事にコンパイルエラー(または、運が悪いとコンパイルが通ってしまって正しく動かない)になってしまうことでしょう。
理由は、上記のvisitor.visit(this);ではvisit(AbstractNode)を呼び出すことが静的に決定されてしまうからです。
従って、メソッドオーバーロードでVisitorを定義するのは構わないのですが、各Visiteeはそのクラス毎に、
public void accept(Visitor visitor) {
visitor.visit(this);
}
の定義が必要になるという事です。
ややこしいと思ったらVisitorのメソッドはVisiteeの実装クラスに応じて別の名前にした方が良いでしょう。

Node達は自分に対する操作を外部から行えるようにするためしかたなくカプセル化を緩くしないといけないというケースがありえます。
# C++ならfriendを使えますね。

使えそうな場面は(適用可能性)

オブジェクトの集合があるとき、その各Nodeに処理を追加したい、また、今後も処理を増やすだろうと予測される場合です。

オブジェクトの集合に含まれる型の方が頻繁に追加/変更されるような場合は、それらに対する処理を分離したことが徒(あだ)となり、分離されたVisitorサブクラスの全てに変更をいれなければならなくなってしまいます。

Double Dispatch

Visitorによって得られる効果の一つ(というよりVisitorはDouble Dispatchの実現といえる)で、二つの対象オブジェクトの型に応じて処理内容を切り替えることをいいます。
Double Dispatchを言語で実現しているものもあるそうです。

つまり、実際に実行されるメソッドは、Visitee実装クラスの型(操作対象)と、Visitor実装クラスの型(処理種別)、及びメソッドシグネチャ(Visiteeのメソッド)によって決定されるということです。