当前位置: 首页 > 知识库问答 >
问题:

为什么我们需要在访问者模式中的接受(),为什么我们不能直接调用visitor.visit()?

常温文
2023-03-14

我正在修改我以前使用的Visitor模式。我们有基类Element,它有虚拟方法接受(Visitor),并且这个方法在继承自Element的所有类中都被重写。在任何派生类中,接受()所做的就是调用visitor-

Visitor& theVisitor = *new ConcreteVisitor();    
for_each(elements.begin(), elements.end(), [](Element& e) { e.accept(theVisitor));})

为什么客户不能直接打电话给访客-

Visitor& theVisitor = *new ConcreteVisitor();    
for_each(elements.begin(), elements.end(), [&theVisitor](Element& e) { theVisitor.visit(e); });

调用元素中有哪些有用的信息。接受(访客),然后依次呼叫访客。参观(元素)?这使得Visitor模式的使用很麻烦,并且在所有元素类的层次结构中都需要额外的代码

有人能在这里解释一下accept()的好处吗?

共有1个答案

贺恩
2023-03-14

我一直对访客模式感到困惑,我一直试图在互联网上找到解释,这些解释让我更加困惑。现在我意识到为什么需要访客模式以及它是如何实现的,所以这里是:

需要采用访客模式来解决双重调度问题。

单一分派-当你有一个类层次结构,并且在这个层次结构中有一个具体类的实例,并且你想为这个实例调用一个合适的方法。这是通过函数重写(在C中使用虚拟函数表)来解决的。

双重分派是指当您有两个类层次结构,一个层次结构中有一个具体类实例,另一个层次结构中有一个具体类实例,并且您希望调用适当的方法来完成这两个特定实例的工作。

我们来看一个例子。

第一等级:动物。基础:动物,衍生:哺乳动物。第二类层次结构:调用程序。基础:Invoker,派生:MovementInvoker(移动动物)、VoiceInvoker(使动物发声)、FeedingInvoker(喂养动物)。

现在,对于每个特定的动物和每个特定的调用程序,我们只希望调用一个特定的函数来完成特定的工作(例如喂鸟或给鱼发声)。所以我们总共有3x3=9个函数来做这些工作。

另一件重要的事:运行这9个函数的客户机不想知道他或她手头上有什么具体的动物,以及什么具体的调用程序

因此,客户想要做如下事情:

void act(Animal& animal, Invoker& invoker)
{
  // Do the job for this specific animal using this specific invoker
}

或者:

void act(vector<shared_ptr<Animal>>& animals, vector<shared_ptr<Invoker>>& invokers)
{
    for(auto& animal : animals)
    {
        for(auto& invoker : invokers)
        {
            // Do the job for this specific animal and invoker.
        }
    }
}

现在:如何在运行时调用9个(或任何)特定方法中的一个,来处理这个特定的动物和这个特定的调用程序?

这是双重调度。您绝对需要从第一类层次结构中调用一个虚拟函数,从第二类层次结构中调用一个虚拟函数。

因此,您需要调用Animal的虚拟方法(使用虚拟函数表,这将在Animal类层次结构中找到具体实例的具体函数),还需要调用Invoker的虚拟方法(这将找到具体的Invoker)。

你必须称之为两种虚拟方法。

下面是实现(您可以复制并运行,我用g编译器对其进行了测试):

来访者h:

#ifndef __VISITOR__
#define __VISITOR__

struct Invoker; // forward declaration;

// -----------------------------------------//

struct Animal
{
    // The name of the function can be anything of course.
    virtual void accept(Invoker& invoker) = 0;
};

struct Fish : public Animal
{
    void accept(Invoker& invoker) override;
};

struct Mammal : public Animal
{
    void accept(Invoker& invoker) override;
};

struct Bird : public Animal
{
    void accept(Invoker& invoker) override;
};

// -----------------------------------------//

struct Invoker
{
  virtual void doTheJob(Fish&   fish)   = 0;
  virtual void doTheJob(Mammal& Mammal) = 0;
  virtual void doTheJob(Bird&   Bird)   = 0;
};

struct MovementInvoker : public Invoker
{
  void doTheJob(Fish&   fish)   override;
  void doTheJob(Mammal& Mammal) override;
  void doTheJob(Bird&   Bird)   override;
};

struct VoiceInvoker : public Invoker
{
  void doTheJob(Fish&   fish)   override;
  void doTheJob(Mammal& Mammal) override;
  void doTheJob(Bird&   Bird)   override;
};

struct FeedingInvoker : public Invoker
{
  void doTheJob(Fish&   fish)   override;
  void doTheJob(Mammal& Mammal) override;
  void doTheJob(Bird&   Bird)   override;
};

#endif

visitor.cpp:

#include <iostream>
#include <memory>
#include <vector>
#include "visitor.h"
using namespace std;

// -----------------------------------------//

void Fish::accept(Invoker& invoker)
{
    invoker.doTheJob(*this);
}

void Mammal::accept(Invoker& invoker)
{
    invoker.doTheJob(*this);
}

void Bird::accept(Invoker& invoker)
{
    invoker.doTheJob(*this);
}

// -----------------------------------------//

void MovementInvoker::doTheJob(Fish& fish)
{
    cout << "Make the fish swim" << endl;
}

void MovementInvoker::doTheJob(Mammal& Mammal)
{
    cout << "Make the mammal run" << endl;
}

void MovementInvoker::doTheJob(Bird& Bird)
{
    cout << "Make the bird fly" << endl;
}

// -----------------------------------------//

void VoiceInvoker::doTheJob(Fish& fish)
{
    cout << "Make the fish keep silence" << endl;
}

void VoiceInvoker::doTheJob(Mammal& Mammal)
{
    cout << "Make the mammal howl" << endl;
}

void VoiceInvoker::doTheJob(Bird& Bird)
{
    cout << "Make the bird chirp" << endl;
}

// -----------------------------------------//

void FeedingInvoker::doTheJob(Fish& fish)
{
    cout << "Give the fish some worms" << endl;
}

void FeedingInvoker::doTheJob(Mammal& Mammal)
{
    cout << "Give the mammal some milk" << endl;
}

void FeedingInvoker::doTheJob(Bird& Bird)
{
    cout << "Give the bird some seed" << endl;
}

int main()
{
    vector<shared_ptr<Animal>> animals = { make_shared<Fish>   (),
                                           make_shared<Mammal> (),
                                           make_shared<Bird>   () };

    vector<shared_ptr<Invoker>> invokers = { make_shared<MovementInvoker> (),
                                             make_shared<VoiceInvoker>    (),
                                             make_shared<FeedingInvoker>  () };

    for(auto& animal : animals)
    {
        for(auto& invoker : invokers)
        {
            animal->accept(*invoker);
        }
    }
}

上述代码的输出:

Make the fish swim
Make the fish keep silence
Give the fish some worms
Make the mammal run
Make the mammal howl
Give the mammal some milk
Make the bird fly
Make the bird chirp
Give the bird some seed

那么,当客户端获得Animal的实例和Invoker的实例并调用Animal时会发生什么呢。接受(调用者)

假设Animal的实例是BirdInvoker的实例是FeedingInvoker

然后感谢虚拟函数表Bird::accept(调用程序

因此,我们进行了双重调度,并为BirdFeedingInvoker调用了正确的方法(9种可能的方法之一)。

为什么访客模式好?

>

  • 客户端不需要同时依赖于动物和调用者的复杂类层次结构。

    如果需要添加新的具体动物(例如,昆虫),则不需要更改现有的动物层次结构。我们只需要添加:doTheJob(昆虫)

    Visitor模式优雅地实现了面向对象设计的开放/封闭原则:系统应该对扩展开放,对修改关闭。

    (在经典的访问者模式中,Invoker将被Visitor取代,doTheJob()将被visit()取代,但对我来说,这些名称实际上并不反映某些工作是在元素上完成的)。

  •  类似资料:
    • 类,这三个类从它继承:。 应用程序希望在12AM到达时发送一条消息。 书中推荐观察者模式。指出如果增加新的类:扩展消息,会影响类时钟的实现。但我不明白为什么。类Clock将保存对象的集合,如果我们要添加一个新的inherit类,它不会更改Clock类。 如果有人能解释上面的例子,或者给出一个更好的例子,我将不胜感激。

    • 问题内容: 使用JDBC连接池工具(如DBCP或c3p0)有什么好处? 如果只有 一个* 用户的 小型CRUD 应用程序,我们是否可以将 一个 连接会话创建为一个 单例 ? * PS :我正在构建一个带有小型数据库(5个表)的小型后端应用程序。 问题答案: 从Jon Skeet的答案到连接和语句池的好处是什么?: 创建到数据库服务器的网络连接是(相对)昂贵的。同样,要求服务器准备SQL语句(相对)

    • 问题内容: 训练期间需要调用该方法。但是文档不是很有帮助 为什么我们需要调用此方法? 问题答案: 在中,我们需要在开始进行反向传播之前将梯度设置为零,因为PyTorch 会 在随后的向后传递中 累积梯度 。在训练RNN时这很方便。因此,默认操作是在每次调用时累积(即求和)梯度。 因此,理想情况下,当您开始训练循环时,应该正确进行参数更新。否则,梯度将指向预期方向以外的其他方向,即朝向 最小值 (或

    • 我是Hadoop和编程的新手,我对Avro模式演变有点困惑。我将解释到目前为止我对Avro的理解。 Avro是一种串行化工具,它存储二进制数据,其json模式位于顶部。模式如下所示。 现在我的问题是为什么我们需要进化?我已经了解到,我们可以在新字段的模式中使用<code>default</code>选项;但是,如果我们在文件中添加一个新的模式,早期的模式将被覆盖。一个文件不能有两个架构。 另一个问

    • 问题内容: Angular应用使用属性而不是事件。 为什么是这样? 问题答案: ng-click包含一个角度表达式。Angular表达式是在Angular 范围的上下文中求值的,该范围绑定到具有ng- click属性的元素或该元素的祖先。 Angular表达式语言不包含流控制语句,也不能声明变量或定义函数。这些限制意味着模板只能访问由控制器或指令提供的变量和运行功能。

    • 以我的拙见,关于“什么是单子”这个著名问题的答案,尤其是投票最多的答案,试图解释什么是单子,而没有明确解释为什么单子是真正必要的。它们能被解释为一个问题的解决方案吗?