Visitor パターンは、オブジェクト指向プログラミング およびソフトウェア工学 において、 アルゴリズムをオブジェクトの構造から分離するためのデザインパターンである。分離による実用的な結果として、既存のオブジェクトに対する新たな操作を構造を変更せずに追加することができる。
基本的には Visitor パターンは一群のクラスに対して新たな仮想関数をクラス自体を変更せずに追加できるようにする。そのために、全ての仮想関数を適切に特化させた Visitor クラスを作成する。Visitor はインスタンスへの参照を入力として受け取り、ダブルディスパッチを用いて目的を達する。
Visitor は強力であるが、既存の仮想関数と比較して制限もある。各クラス内に小さなコールバックメソッドを追加する必要があり、各クラスのコールバックメソッドは新たなサブクラスで継承することができない。
Visitor パターンの基本的な考えとして、visitor
を引数として受け取る accept()
メソッドを持ったクラスを複数、要素として持つ構造をとる。Visitor
は各要素クラスごとにaccept()
メソッドを持つインタフェース である。特定の処理を実行する個別の具体的なvisitor
クラスを作成することができる。具体的な visitor
の visit()
メソッドは、一つのクラスのメソッドとして考えるものではなく、二つのクラスのペア、すなわち具体的な visitor と特定の要素クラスに対するメソッドとして考えることができる。それゆえ、Visitor パターンは ダブルディスパッチ を従来の Java、Smalltalk、C++ などのオブジェクト指向プログラミング言語で模したものである。ダブルディスパッチと関数のオーバーロードとの違いについては、en:Double dispatch#Double dispatch is more than function overloadingの記事を参照のこと。Java 言語でリフレクションを用いて Visitor パターンにおけるダブルディスパッチを模倣する機構を簡潔にする二つのテクニックが文書として公開されている(
getting rid of accept() methods (Walkabout の派生形)および getting rid of extra visit() methods )。
Visitor パターンは、オブジェクトの構造に繰り返しをどのように行うかを指定する。最も簡単な場合、各アルゴリズムが繰り返しを全く同じように行う場合、コンテナ要素の accept()
メソッドは、visitor
の visit()
メソッドをコールバックするだけではなく、visitor
オブジェクトを 全ての子要素の accept()
メソッドに渡す。
Visitor オブジェクトは主たる関数(複数の特化したメソッドで明示されている)を一つ持ち、その関数は visit()
と呼ばれる。それゆえ、Visitor は関数オブジェクトやファンクタとして捉えることも容易である。同様に、 accept()
関数は、特定の型のオブジェクトを順次走査して各要素に関数を適用する方法を知っている利用者、マッパーである。Common Lisp は 多重ディスパッチをサポートする数少ないオブジェクト指向システムではあるが、上記の例の最初の部分は多重ディスパッチに依存しない。多重ディスパッチを備えた Lisp のオブジェクトシステムは Visitor パターンを置き換えるものではなく、パターンそのものを残したより簡潔な実装を提供するのみである。
UML のクラス図
下記の例は、Java プログラミング言語のものである。
interface Visitor {
void visit(Wheel wheel);
void visit(Engine engine);
void visit(Body body);
void visitCar(Car car);
void visitVehicle(Vehicle vehicle);
}
class Wheel {
private String name;
Wheel(String name) {
this.name = name;
}
String getName() {
return this.name;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Engine {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Body {
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
abstract class Vehicle {
protected Engine engine = new Engine();
protected Body body = new Body();
protected Wheel[] wheels;
public Engine getEngine() {
return this.engine;
}
public Body getBody() {
return this.body;
}
public Wheel[] getWheels() {
return this.wheels;
}
public void accept(Visitor visitor) {
visitor.visitVehicle(this);
}
}
class Car extends Vehicle {
public Car() {
super();
this.wheels = new Wheel[]
{ new Wheel("front left"), new Wheel("front right"),
new Wheel("back left") , new Wheel("back right") };
}
public void accept(Visitor visitor) {
visitor.visitCar(this);
}
}
class PrintVisitor implements Visitor {
public void visit(Wheel wheel) {
System.out.println("Visiting "+ wheel.getName()
+ " wheel");
}
public void visit(Engine engine) {
System.out.println("Visiting engine");
}
public void visit(Body body) {
System.out.println("Visiting body");
}
public void visitVehicle(Vehicle vehicle) {
System.out.println("Visiting vehicle");
}
public void visitCar(Car car) {
System.out.println("Visiting car");
car.getEngine().accept(this);
car.getBody().accept(this);
for(Wheel wheel : car.getWheels()) {
wheel.accept(this);
}
}
}
class DoVisitor implements Visitor {
public void visit(Wheel wheel) {
System.out.println("Steering my wheel");
}
public void visit(Engine engine) {
System.out.println("Starting my engine");
}
public void visit(Body body) {
System.out.println("Moving my body");
}
public void visitCar(Car car) {
System.out.println("Starting my car");
car.getEngine().accept(this);
car.getBody().accept(this);
for(Wheel wheel : car.getWheels()) {
wheel.accept(this);
}
}
public void visitVehicle(Vehicle vehicle) {
System.out.println("Starting my vehicle");
}
}
public class VisitorDemo {
static public void main(String[] args){
Car car = new Car();
Visitor printVisitor = new PrintVisitor();
Visitor doVisitor = new DoVisitor();
car.accept(printVisitor);
car.accept(doVisitor);
}
}
下記の例は、C++ プログラミング言語のものである。
#include <iostream>
#include <ostream>
#include <vector>
class Wheel;
class Engine;
class Body;
class Car;
class Visitor {
public:
Visitor() { }
virtual ~Visitor() { }
virtual void visit(Wheel &) = 0;
virtual void visit(Engine &) = 0;
virtual void visit(Body &) = 0;
virtual void visit(Car &) = 0;
};
class TestVisitor : public Visitor {
public:
TestVisitor() : Visitor() { }
virtual ~TestVisitor() { }
virtual void visit(Wheel &) {
std::cout << "Visiting Wheel" << std::endl;
}
virtual void visit(Engine &) {
std::cout << "Visiting Engine" << std::endl;
}
virtual void visit(Body &) {
std::cout << "Visiting Body" << std::endl;
}
virtual void visit(Car &) {
std::cout << "Visiting Car" << std::endl;
}
};
class CarElement {
public:
CarElement() { }
virtual ~CarElement() { }
virtual void accept(Visitor &) = 0;
};
class Wheel : public CarElement {
public:
virtual void accept(Visitor & visitor) {
visitor.visit(*this);
}
};
class Engine : public CarElement {
public:
virtual void accept(Visitor & visitor) {
visitor.visit(*this);
}
};
class Body : public CarElement {
public:
virtual void accept(Visitor & visitor) {
visitor.visit(*this);
for (std::vector<Wheel>::iterator i = _wheels.begin(); i != _wheels.end(); ++i) {
i->accept(visitor);
}
}
private:
std::vector<Wheel> _wheels;
};
class Car : public CarElement {
public:
virtual void accept(Visitor & visitor) {
visitor.visit(*this);
_engine.accept(visitor);
_body.accept(visitor);
}
private:
Engine _engine;
Body _body;
};
int main() {
Car car;
TestVisitor visitor;
car.accept(visitor);
}
Visitor パターンは関心の分離をより進展させる可能性があるが、加えて単純に多態的なメソッドを呼び出す以上の利点として、Visitor パターンでは状態を持つことができる。これは、オブジェクトに対して実行されるアクションが以前のアクションに依存するような多くのケースで極めて有用である。
この例は、pretty-printer のプログラミング言語 による実装(コンパイラやインタプリタによる)である。pretty-printer オブジェクト (本例では Visitor として実装されている)は、パースされ、処理されたデータ構造内のノードを巡回する。pretty-printer は次に、プログラムツリーのテキストによる表現を生成する。出力を人間が可読なものにするために、pretty-printer はプログラムの文や表現を適切にインデントしなければならない。「現在のインデント幅」が visitor により状態の一つとして追跡され、単純な多態的な関数の呼び出しとして、カプセル化が正しく行われ、インデント幅はパラメータとして公開され、呼び出し者はこのパラメータを正しく使用するためのメソッドの実装を信頼する。