1

タイプ「動物」の抽象オブジェクトがあるとします。Animal はパブリックな純粋仮想メソッド「eat」を持っています。

Animal を "Dog" と "Cat" に派生させ、それぞれに拡張インターフェイスを持たせたいと考えています。たとえば、Dog にはパブリック メソッド「chaseTail」を、Cat にはパブリック メソッド「destroyFurniture」を持たせたいとします。

「World」オブジェクトに動物のコレクションを作りたいです。

「getAnimalAtPosition」メソッドを使用して World からこれらの動物を取得できる必要があり、取得した動物に対して任意に chaseTail または destroyFurniture を呼び出すことができる必要があります。

位置が指定された動物であるかどうかをテストしたり、chaseTail と destroyFurniture を動物に巻き上げたりして、遅い dynamic_cast を回避したいのですが、ここでコーナーに戻っているようです。

別の方法はありますか?

4

3 に答える 3

4

訪問者パターンは実行可能なソリューションです。このソリューションには、主に 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受け入れる純粋仮想メソッドを に追加します。アイデアは、 を に渡し、仮想メソッドが特定のランタイム タイプに解決できるようにすることです。仮想呼び出しが解決されると、実装は で特定のメソッドを呼び出すことができます。Visitorvoid Animal::accept( Vistor& )VisitorAnimalvisitVisitor

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 クラスで操作を定義する方が簡単な場合があります。この例では、CowHorse、およびを追加する必要がある場合は、仮想メソッドをPigに追加する方が簡単な場合があります。doTypicalAnimal
  • Element 階層が安定しているが、Elements で動作するアルゴリズムが変化している場合は、Visitor パターンが適切な候補になる可能性があります。
于 2012-06-20T19:50:28.780 に答える
1

すべてのクラスの完全な知識があれば、ビジターパターンを実行できます。

于 2012-06-20T18:05:18.433 に答える
0

実行時にポリモーフィック階層の動的な型を発見したいという欲求は、通常、設計エラーを示しています。代わりに、次のようなことを試すことができます。

struct Animal
{
    virtual ~Animal()             {              }
    void eat()                    { doEat();     }
    void performTypicalActivity() { doTypical(); }
private:
    virtual void doEat() = 0;
    virtual void doTypical() = 0;
};

struct Cat : Animal
{
    void destroyFurniture();
    void purr();
private:
    virtual void doTypical() { destroyFurniture(); purr(); }
};

コレクションを繰り返し処理し、p->performTypicalActivity()すべての要素を呼び出します。

于 2012-06-20T18:11:21.773 に答える