28

所以我已经阅读了有关访问者模式的所有文档,但我仍然非常困惑。我从另一个 SO question 中举了这个例子,有人可以帮助我理解吗?例如,我们什么时候使用访问者设计模式?我想我可能已经理解了一些,但我只是无法看到更大的图景。我怎么知道什么时候可以使用它?

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);
4

2 回答 2

38

访问者模式用于实现双重调度。简而言之,这意味着执行的代码取决于两个对象的运行时类型。

当你调用一个普通的虚函数时,它是一个单一的调度:被执行的代码段取决于一个对象的运行时类型,即你正在调用的那个虚方法。

对于访问者模式,被调用的方法最终取决于两个对象的类型——实现 的对象equipmentVisitor的类型,以及您调用的对象的类型accept(即equipmentVisited子类)。

还有其他方法可以在 C++ 中实现双重分派。Scott Meyer 的“More Effective C++”的第 31 项深入探讨了这个主题。

于 2012-04-12T01:10:12.273 回答
15

我认为Visitor这个模式的名字是相当不幸的。而不是访问者这个词,我会说 Functor 或 Operator,而不是“访问”,我会说“应用”。

我对访问者模式的理解如下:

在模板元编程(STL/BOOST)(编译时绑定)中,您可以通过函数对象(Functors)实现(正交设计)操作与结构的分离。例如在

template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

comp 是一个以非常通用的方式表示“小于”操作的函子/运算符,因此您不必有许多 sort 函数的变体:

对于访问者模式,您希望实现类似但在运行时(后期)绑定的情况下:

你想简化 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_or_Operator 拥有非常通用的接口来减少基类 A 中的函数数量:

 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 层次结构 (A,B,C) 中有 N(=3) 个类和 M 个操作或由 Functor_or_Operator 层次结构中的类表示的函数您需要实现 N*M 定义来定义 Functor_or_Operator 中的每个操作如何作用于每个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*) {}
  }
于 2014-02-01T04:21:42.987 回答