且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

具有扩展接口的派生类的集合.如何在没有动态转换的情况下访问派生接口?

更新时间:2023-01-11 08:01:31

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

The visitor pattern is a viable solution. The solution has two main participants:

  • 元素:具有共同父级的不同类型,将接受访问者.在这种情况下,元素是 CatDog,共同的父元素是 Animal.
  • 访问者:将访问元素的类,并且可以调用特定于元素的操作,因为它具有特定元素类型的句柄.
  • Elements: The distinct types with a common parent that will accept a Visitor. In this case, the Elements are Cat and Dog with the common parent being Animal.
  • Visitor: The class that will visit Elements, and can invoke Element specific operations as it has a handle to the specific Element type.

对于此示例,从元素(动物、猫和狗)开始:

For this example, begin with the elements (Animal, Cat, and 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()>:

Next, create a Visitor that will 'visit' each Element. The Visitor will know the type it is operating on, so it can use methods on both the specific Elements, such as Cat::destroyFurniture() and 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,并允许虚拟方法解析为特定的运行时类型.一旦虚拟调用被解析,实现就可以调用 Visitor 上的特定 visit 方法.

Now, add a pure virtual method to Animal that accepts a Visitor as an argument: void Animal::accept( Vistor& ). The idea is to pass a Visitor to an Animal, and allow the virtual method to resolve to the specific runtime type. Once the virtual call is resolved, the implementation can invoke the specific visit method on the 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 的情况下根据类型执行分支,通常称为 双重派遣.

Notice how the virtual method is used to resolve to the specific Element type, and that that each element's accept implementation will invoke a method on the Visitor. This allows for execution to branch based on type without the use of dynamic_cast, and is commonly referred to as double dispatch.

这里是一个演示使用模式的可编译示例:

Here is a compilable example that demonstrates the pattern in use:

#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 执行不同的操作.


Please note that in this example, Visitor is a concrete class. However, it is possible for an entire hierarchy to be created for Visitor, allowing you to perform different operations based on the 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 类进行更改.一般的经验法则是:

One major drawback to the Visitor pattern is that adding Elements may require making changes to the Visitor classes. The general rule of thumb is:

  • 如果 Element 层次结构可能会发生变化,则在基本 Element 类上定义操作可能会更容易.在这个例子中,如果需要添加CowHorsePig,那么添加一个虚拟的doTypical可能更容易 方法到 Animal.
  • 如果元素层次结构稳定,但在元素上运行的算法正在发生变化,那么访问者模式可能是一个不错的选择.
  • If the Element hierarchy is likely to change, it may be easier to define operations on the base Element class. In this example, if Cow, Horse, and Pig need to be added, then it may be easier to add a virtual doTypical method to Animal.
  • If the Element hierarchy is stable, but the algorithms operating on the Elements are changing, then the Visitor pattern may be a good candidate.