假设我有一个“动物”类型的抽象对象。动物具有公共(public)纯虚拟方法“吃”。

我想将Animal派生为“狗”和“猫”,每一个都具有扩展的接口(interface)。例如,我希望Dog使用公用方法“chaseTail”,而Cat使用公用方法“destroyFurniture”。

我想在“世界”对象中收集动物。

我需要能够使用“getAnimalAtPosition”方法从“世界”中检索这些动物,并能够在获得的动物上任意调用chaseTail或destroyFurniture。

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

还有另一种方法吗?

最佳答案

visitor pattern是一个可行的解决方案。该解决方案有两个主要参与者:

  • 元素:具有公共(public)父对象的不同类型,它们将接受访客。在这种情况下,元素是CatDog,公共(public)父对象是Animal
  • Visitor:将访问Elements的类,因为它具有特定Element类型的句柄,所以可以调用Element特定的操作。

  • 对于此示例,从元素(动物,猫和狗)开始:
    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中添加一个纯虚拟方法,该方法接受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,通常称为double dispatch

    Here是一个可编译的示例,演示了正在使用的模式:
    #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类上定义操作可能会更容易。在此示例中,如果需要添加CowHorsePig,则将虚拟doTypical方法添加到Animal可能会更容易。
  • 如果Element层次结构稳定,但是对Elements进行操作的算法正在更改,则Visitor模式可能是一个不错的选择。
  • 关于c++ - 具有扩展接口(interface)的派生类的集合。如何在不进行动态强制转换的情况下访问派生接口(interface)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/11125475/

    10-11 23:05