そのため、Visitorパターンに関するすべてのドキュメントを読みましたが、それでも私は非常に混乱しています。この例を別のSO=質問から取り上げました。誰かが私を理解するのを手伝ってくれませんか?たとえば、ビジターのデザインパターンをいつ使用するのですか?私はそれの一部を理解したと思いますが、私はm全体像が見えないのですが、いつ使用できるかを知るにはどうすればよいですか。
class equipmentVisited
{
virtual void accept(equipmentVisitor* visitor) = 0;
}
class floppyDisk : public equipmentVisited
{
virtual void accept(equipmentVisitor* visitor);
}
class processor : public equipmentVisited
{
virtual void accept(equipmentVisitor* visitor);
}
class computer : public equipmentVisited
{
virtual void accept(equipmentVisitor* visitor);
}
class equipmentVisitor
{
virtual void visitFloppyDisk(floppyDisk* );
virtual void visitProcessor(processor* );
virtual void visitComputer(computer* );
}
// Some additional classes inheriting from equipmentVisitor would be here
equipmentVisited* visited;
equipmentVisitor* visitor;
// Here you initialise visited and visitor in any convenient way
visited->accept(visitor);
double dispatch を実装するために訪問者パターンが使用されます。つまり、実行されるコードは、2つのオブジェクトの実行時の型に依存するということです。
通常の仮想関数を呼び出すと、単一のディスパッチになります。実行されるコードは、単一のオブジェクトのランタイムタイプによって異なります。 、呼び出している仮想メソッド。
ビジターパターンでは、呼び出されるメソッドは最終的に2つのオブジェクトのタイプ(equipmentVisitor
を実装するオブジェクトのタイプ、およびaccept
を呼び出すオブジェクトのタイプ)に依存します(つまり、equipmentVisited
サブクラス)。
C++で二重ディスパッチを実装する方法は他にもあります。 Scott Meyerのアイテム31の "More Effective C++" は、この主題を詳細に扱います。
パターンVisitorの名前は非常に残念です。 Wordビジターの代わりにFunctorまたはOperatorと言い、「visit」の代わりに「apply」と言います。
ビジターパターンについての私の理解は次のとおりです。
テンプレートメタプログラミング(STL/BOOST)(コンパイル時バインディング)では、関数オブジェクト(Functor)を使用して、構造から操作を分離(直交設計)できます。
template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
compは、非常に一般的な方法で「小なり」演算を表すファンクター/演算子であるため、ソート関数の多くのバリアントを持つ必要はありません。
Visitorパターンの場合、同様のことを実現したいのですが、実行時(遅延)バインディングの場合:
Aのインターフェースを単純化したい、将来の拡張(Aで動作する新しい操作)の可能性を維持したい、それらの拡張の場合にAのインターフェースの安定性を実現したい。
元の「脂肪」クラスから:
class A
{
public:
virtual void function_or_operation_1();//this can be implemented in terms of public interface of the other functions
virtual void function_or_operation_2();
//..etc
virtual void function_or_operation_N();
public:
//stable public interface, some functions of procedures
private:
//....
}
パブリックインターフェイスからできるだけ多くの関数を削除し(同じパブリックインターフェイスの非抽出関数に関して実装できる限り)、操作をファンクターオブジェクトまたは新しいFunctor階層のオブジェクトとして表します。
基本クラスAの関数の数を減らすには、前方宣言されたFunctor_or_Operatorを使用して非常に一般的なインターフェイスを作成します。
class Functor_or_Operator;
class A
{
public:
virtual void apply(Functor_or_Operator*);//some generic function operates on this objects from A hierarchy
//..etc
public:
//stable public interface, some functions
private:
//....
}
// A(B、C)階層にN(= 3)個のクラスがあり、Functor_or_Operator階層のクラスで表されるM演算または関数があるA階層のクラス。大きなことは、クラス「A」のインターフェースを変更せずにそれを実行できることです。クラス「A」の宣言は、A階層のオブジェクトを処理する新しい操作または関数を導入するときの新しい追加の場合、またはA階層の新しい派生クラスの場合に、非常に安定します。追加が存在する場合のAの安定性(Aへの変更なし)は、多くの場所にAのヘッダーを含むソフトウェアの再コンパイル(および不可能)を回避するために重要です。
A階層内の新しいクラスごとに、ベースFunctor_or_Operatorの定義を拡張します。新しい実装ファイルを追加しますが、ベースクラスA(通常はインターフェイスまたは抽象クラス)のヘッダーを変更する必要はありません。
class Functor_or_Operator
{
virtual void apply(A*)=0;
virtual void apply(B*)=0;
virtual void apply(C*)=0;
}
void A::apply(Functor_or_Operator* f)
{ f->apply(this);} //you need this only if A is not abstract (it is instantiable)
class B:public A
{
public:
void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymhorphic Functor f on this object
//..the rest of B implementation.
}
class C:public A
{
public:
void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymorfic Functor f on this object
//..the rest of C implementation.
}
class Functor_or_Operator_1:public Functor_or_Operator
{
public:
//implementations of application of a function represented by Functor_or_Operator_1 on each A,B,C
void apply(A*) {}//( only if A is instantiable,not an abstract class)
void apply(B*) {}
void apply(C*) {}
}
class Functor_or_Operator_2:public Functor_or_Operator
{
public:
//implementations of application of a function represented by Functor_or_Operator_2 on each A,B,C
void apply(A*) {}//( only if A is instantiable,not an abstract class)
void apply(B*) {}
void apply(C*) {}
}