訪問者パターンは実行可能なソリューションです。このソリューションには、主に 2 つの参加者がいます。
- 要素: 訪問者を受け入れる共通の親を持つ個別の型。この場合、要素は
Cat
でありDog
、共通の親はAnimal
です。
- ビジター: 要素を訪問し、特定の要素タイプへのハンドルを持っているため、要素固有の操作を呼び出すことができるクラス。
この例では、要素 (Animal、Cat、および Dog) から始めます。
class Animal
{
public:
virtual ~Animal() {}
virtual void eat() = 0;
};
class Cat: public Animal
{
public:
void destroyFurniture();
void eat();
};
class Dog: public Animal
{
public:
void chaseTail();
void eat();
};
次に、各要素を「訪問」する Visitor を作成します。ビジターは操作対象の型を認識しているため、 と などの特定の要素の両方でメソッドを使用できCat::destroyFurniture()
ますDog::chaseTail()
。
class Visitor
{
public:
void visitDog( Dog& dog ) { dog.chaseTail(); }
void visitCat( Cat& cat ) { cat.destroyFurniture(); }
};
ここで、 aを引数としてAnimal
受け入れる純粋仮想メソッドを に追加します。アイデアは、 を に渡し、仮想メソッドが特定のランタイム タイプに解決できるようにすることです。仮想呼び出しが解決されると、実装は で特定のメソッドを呼び出すことができます。Visitor
void Animal::accept( Vistor& )
Visitor
Animal
visit
Visitor
class Animal
{
public:
...
virtual void accept( Visitor& ) = 0;
};
class Cat: public Animal
{
public:
...
virtual void accept( Visitor& visitor ) { visitor.visitCat( *this ); }
};
仮想メソッドを使用して特定の Element タイプを解決する方法と、各要素のaccept
実装が Visitor でメソッドを呼び出すことに注意してください。これにより、 を使用せずにタイプに基づいて分岐する実行が可能になり、dynamic_cast
一般にダブル ディスパッチと呼ばれます。
以下は、使用中のパターンを示すコンパイル可能な例です。
#include <iostream>
#include <vector>
using std::cout;
using std::endl;
class Cat;
class Dog;
class Visitor
{
public:
void visitCat( Cat& cat );
void visitDog( Dog& dog );
};
class Animal
{
public:
virtual ~Animal() {}
virtual void eat() = 0;
virtual void accept( Visitor& ) = 0;
};
class Cat: public Animal
{
public:
void destroyFurniture() { cout << "Cat::destroyFurniture()" << endl; }
void eat() { cout << "Cat::eat()" << endl; }
void accept( Visitor& visitor ) { visitor.visitCat( *this ); }
};
class Dog: public Animal
{
public:
void chaseTail() { cout << "Dog::chaseTail()" << endl; }
void eat() { cout << "Dog::eat()" << endl; }
void accept( Visitor& visitor ) { visitor.visitDog( *this ); }
};
// Define Visitor::visit methods.
void Visitor::visitCat( Cat& cat ) { cat.destroyFurniture(); }
void Visitor::visitDog( Dog& dog ) { dog.chaseTail(); }
int main()
{
typedef std::vector< Animal* > Animals;
Animals animals;
animals.push_back( new Cat() );
animals.push_back( new Dog() );
Visitor visitor;
for ( Animals::iterator iterator = animals.begin();
iterator != animals.end(); ++iterator )
{
Animal* animal = *iterator;
// Perform operation on base class.
animal->eat();
// Perform specific operation based on concrete class.
animal->accept( visitor );
}
return 0;
}
次の出力が生成されます。
Cat::eat()
Cat::destroyFurniture()
Dog::eat()
Dog::chaseTail()
Visitor
この例では、 が具象クラスであることに注意してください。ただし、 に対して階層全体を作成してVisitor
、 に基づいてさまざまな操作を実行することができますVisitor
。
class Visitor
{
public:
virtual void visitCat( Cat& ) = 0;
virtual void visitDog( Dog& ) = 0;
};
class FurnitureDestroyingVisitor: public Visitor
{
virtual void visitCat( Cat& cat ) { cat.destroyFurniture(); }
virtual void visitDog( Dog& dog ) {} // Dogs cannot destroy furniture.
};
Visitor パターンの主な欠点の 1 つは、追加するためにクラスElements
の変更が必要になる場合があることです。Visitor
一般的な経験則は次のとおりです。
- Element 階層が変更される可能性がある場合は、基本 Element クラスで操作を定義する方が簡単な場合があります。この例では、
Cow
、Horse
、およびを追加する必要がある場合は、仮想メソッドをPig
に追加する方が簡単な場合があります。doTypical
Animal
- Element 階層が安定しているが、Elements で動作するアルゴリズムが変化している場合は、Visitor パターンが適切な候補になる可能性があります。