異なるクラスのオブジェクトに対する同種の操作を切り出して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に対する新たな操作を付け加えたことになります。
abstract class AbstractNode implements Visitee {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Node1 extends AbstractNode {
:
}
しかし、これをもとにNode1やNode2などを定義するように変更すると、見事にコンパイルエラー(または、運が悪いとコンパイルが通ってしまって正しく動かない)になってしまうことでしょう。public void accept(Visitor visitor) {
visitor.visit(this);
}
の定義が必要になるという事です。Node達は自分に対する操作を外部から行えるようにするためしかたなくカプセル化を緩くしないといけないというケースがありえます。
# C++ならfriendを使えますね。
オブジェクトの集合があるとき、その各Nodeに処理を追加したい、また、今後も処理を増やすだろうと予測される場合です。
オブジェクトの集合に含まれる型の方が頻繁に追加/変更されるような場合は、それらに対する処理を分離したことが徒(あだ)となり、分離されたVisitorサブクラスの全てに変更をいれなければならなくなってしまいます。
Visitorによって得られる効果の一つ(というよりVisitorはDouble Dispatchの実現といえる)で、二つの対象オブジェクトの型に応じて処理内容を切り替えることをいいます。
Double Dispatchを言語で実現しているものもあるそうです。
つまり、実際に実行されるメソッドは、Visitee実装クラスの型(操作対象)と、Visitor実装クラスの型(処理種別)、及びメソッドシグネチャ(Visiteeのメソッド)によって決定されるということです。