C++ 为什么我们需要访问者模式中的accept(),为什么我们不能直接调用Visitor.visit()?
我正在修改前一段时间使用的访问者模式。我们有一个基类元素,它有一个虚拟方法accept(Visitor),这个方法在从元素继承的所有类中都被重写。accept()在任何派生类中所做的就是调用visitor->visit(*this)。现在,当客户机运行代码时,他/她会执行以下操作,例如:C++ 为什么我们需要访问者模式中的accept(),为什么我们不能直接调用Visitor.visit()?,c++,design-patterns,visitor,double-dispatch,C++,Design Patterns,Visitor,Double Dispatch,我正在修改前一段时间使用的访问者模式。我们有一个基类元素,它有一个虚拟方法accept(Visitor),这个方法在从元素继承的所有类中都被重写。accept()在任何派生类中所做的就是调用visitor->visit(*this)。现在,当客户机运行代码时,他/她会执行以下操作,例如: Visitor& theVisitor = *new ConcreteVisitor(); for_each(elements.begin(), elements.end(), [](Eleme
Visitor& theVisitor = *new ConcreteVisitor();
for_each(elements.begin(), elements.end(), [](Element& e) { e.accept(theVisitor));})
为什么客户端不能像这样调用visitor->visit(元素):
Visitor& theVisitor = *new ConcreteVisitor();
for_each(elements.begin(), elements.end(), [&theVisitor](Element& e) { theVisitor.visit(e); });
调用element.accept(visitor)中有哪些有用的信息,它依次调用visitor.visit(element)?这使得访问者模式的使用非常麻烦,并且在所有元素类的层次结构中都需要额外的代码
那么,有人能在这里解释一下accept()的好处吗?我一直对访问者模式感到困惑,我一直试图在互联网上找到解释,这些解释让我更加困惑。现在我意识到了为什么需要访问者模式,以及它的实现方式,所以这里是: 为了解决双重调度问题,需要引入访问者模式 单一分派-当您有一个类层次结构,并且在此层次结构中有一个具体类的实例时 您希望为此实例调用适当的方法。这可以通过函数重写(使用C++中的虚拟函数表)来解决 双重分派是指当您有两个类层次结构,一个层次结构中有一个具体类实例,另一个层次结构中有一个具体类实例,并且您希望调用适当的方法来执行这两个特定实例的工作 让我们看一个例子 第一等级:动物。基础:
动物
,衍生:鱼
,哺乳动物
,鸟
。
第二类层次结构:调用程序。基础:Invoker
,派生:MovementInvoker
(移动动物)、VoiceInvoker
(使动物发声)、FeedingInvoker
(喂养动物)
现在,对于每个特定的动物和每个特定的调用程序,我们只希望调用一个特定的函数来完成特定的工作(例如,喂鸟或给鱼发声)。所以我们总共有3x3=9个函数来做这些工作
另一件重要的事:运行这9个函数的客户机不想知道他或她手头上有什么具体的Animal
和什么具体的Invoker
因此,客户希望执行以下操作:
void act(Animal& animal, Invoker& invoker)
{
// Do the job for this specific animal using this specific invoker
}
或:
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);
}
}
}
那么,当客户端获得Animal
的实例和Invoker
的实例并调用Animal.accept(Invoker)
时会发生什么呢
假设Animal
的实例是Bird
,而Invoker
的实例是FeedingInvoker
然后,由于虚拟函数表Bird::accept(Invoker&)
将被调用,它将依次运行Invoker.doTheJob(Bird&)
。
由于Invoker
实例是FeedingInvoker
,虚拟函数表将使用FeedingInvoker::accept(Bird&)
进行此调用
因此,我们进行了双重调度,并为Bird
和FeedingInvoker
调用了正确的方法(9种可能的方法之一)
为什么访客模式好
昆虫
),则不需要更改现有的动物
层次结构。
我们只需要将:doTheJob(昆虫和昆虫)
添加到Invoker
和所有派生的调用程序(在经典的访问者模式中,
Invoker
将被Visitor
替换,而doTheJob()
将被visit()
替换,但对我来说,这些名称实际上并不反映某些工作是在元素上完成的)。好处在于信息隐藏,因此元素不需要显示其实现。假设一个元素可能有,也可能没有——这是特定于实现的——有子元素,它们也必须被访问。按照您的想法,元素需要有一个getter方法“getSubElements”,以便您可以访问它们。但是使用前一种思想,accept方法可以被实现来访问它的所有子元素,而不需要透露这个实现细节。好的,你的意思是元素有一些私有方法/变量,Visitor是元素的一个朋友类,所以Visitor可以调用元素的私有方法。但在访问者模式的描述/示例中从未提到过这一点。是的,当然,基本元素类中的Element.accept()可以调用补遗的accept(),但这只是在这里添加了另一种模式,例如模板方法。这不是访问者模式的一部分,这使得它很难理解。不需要调用私有方法(这会破坏信息隐藏)。事实上,为了获得所需的行为,必须重写accept方法。元素声明一个accept方法来接受访问者,并将访问者作为参数。从元素类派生的具体元素实现accept方法。在最简单的形式中,这只不过是对访问者的visit方法的调用。复合元素维护子对象的列表,通常在这些子对象上进行迭代alling每个孩子的accept方法。“好吧,我明白了原因:访问者模式实现了双重分派。因此,应该在第一个类层次结构中调用虚拟方法,在第二个类层次结构中调用虚拟方法。因此,这两个类层次结构的虚拟函数表将用于查找两个具体对象,该方法将为其
#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
#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