1

假设我有一个“动物”类型的抽象对象。动物有公纯虚法“吃”。

我想将 Animal 派生为“Dog”和“Cat”,每个都有一个扩展接口。例如,我希望 Dog 有一个公共方法“chaseTail”,而 Cat 有一个公共方法“destroyFurniture”。

我想在“世界”对象中制作一组动物。

我需要能够使用“getAnimalAtPosition”方法从 World 中检索这些动物,并且能够在我得到的 Animal 上任意调用 chatTail 或 destroyFurniture。

我想避免缓慢的dynamic_cast,测试位置是否是给定的动物或将chaseTail和destroyFurniture提升到Animal中,但我似乎在这里陷入了困境。

还有其他方法吗?

4

3 回答 3

4

访问者模式是一个可行的解决方案。该解决方案有两个主要参与者:

  • 元素:具有共同父级的不同类型,将接受访问者。在这种情况下,元素是CatDog共同的父母是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();
};

接下来,创建一个将“访问”每个元素的访客。访问者将知道它正在操作的类型,因此它可以在两个特定元素上使用方法,例如Cat::destroyFurniture()Dog::chaseTail()

class Visitor
{
public:
   void visitDog( Dog& dog ) { dog.chaseTail();        }
   void visitCat( Cat& cat ) { cat.destroyFurniture(); }
};

Animal现在,添加一个接受 aVisitor作为参数的纯虚方法void Animal::accept( Vistor& ):这个想法是将 a 传递给 a VisitorAnimal并允许虚拟方法解析为特定的运行时类型。一旦解决了虚拟调用,实现就可以visit调用Visitor.

class Animal
{
public:
  ...
  virtual void accept( Visitor& ) = 0;
};

class Cat: public Animal
{
public:
  ...
  virtual void accept( Visitor& visitor ) { visitor.visitCat( *this ); }
};

请注意虚拟方法如何用于解析特定的元素类型,并且每个元素的accept实现都将调用访问者上的方法。这允许执行基于类型分支而不使用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.
};

访问者模式的一个主要缺点是添加Elements可能需要对Visitor类进行更改。一般的经验法则是:

  • 如果 Element 层次结构可能发生变化,则在基本 Element 类上定义操作可能会更容易。在此示例中,如果需要添加 、 和 ,则向 中添加虚拟方法可能CowHorse更容易。PigdoTypicalAnimal
  • 如果元素层次结构是稳定的,但在元素上运行的算法正在发生变化,那么访问者模式可能是一个很好的候选者。
于 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 回答