我正在修改一段时间前我使用的访问者模式。我们有基类元素,它具有虚拟方法accept(访问者),并且该方法在所有继承自元素的类中都被重写。在任何派生类中,接受()所做的就是调用访问者->访问(*this)。现在,当客户端运行代码时,他/她会这样做,例如:
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); });调用element.accept(访问者)时有什么有用的信息,后者依次调用visitor.visit(元素)?这使得访问者模式的使用变得非常麻烦,并且需要在元素类的所有层次结构中添加额外的代码。
那么有人能在这里解释接受()的好处吗?
发布于 2018-05-24 12:28:08
长期以来,我一直对访问者的模式感到困惑,我一直试图在互联网上找到解释,而这些解释更让我感到困惑。现在,我意识到了为什么需要访问者模式,以及它是如何实现的,如下所示:
访客模式在需要解决的双重调度问题。
单个分派--当您有一个类层次结构,并且在这个层次结构中有一个具体类的实例时,您希望为这个实例调用一个适当的方法。这是通过函数重写来解决的(在C++中使用虚拟函数表)。
双重分派是指当您有两个类层次结构,一个具体类实例来自一个层次结构,一个实例具体类实例来自另一个层次结构时,您希望调用适当的方法来完成这两个特定实例的工作。
让我们看一个例子。
一级等级:动物。碱基:Animal,衍生:Fish,Mammal,Bird。二级层次结构:邀请者。基础: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
}或者:
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.
}
}
}现在:如何在运行时调用处理此特定Animal和特定Invoker的9(或其他特定)方法之一。
双重调度来了。您绝对需要从第一类层次结构调用一个虚拟函数,从第二个层次结构调用一个虚拟函数。
因此,您需要调用Animal的虚拟方法(使用虚拟函数表,这将在Animal类层次结构中找到具体实例的具体函数),还需要调用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;
};
#endifvisitor.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.accept(invoker)时,会发生什么?
假设Animal的实例是Bird,Invoker的实例是FeedingInvoker。
然后,由于有了虚拟函数表,Bird::accept(Invoker&)将被调用,这将反过来运行invoker.doTheJob(Bird&)。由于Invoker实例是FeedingInvoker,因此虚拟函数表将使用FeedingInvoker::accept(Bird&)进行此调用。
因此,我们对Bird和FeedingInvoker进行了双重调度,并调用了正确的方法(9种可能的方法之一)。
为什么访客模式是好的?
Insect),则不需要更改现有的Animal层次结构。我们只需要添加:doTheJob(Insect& insect)到Invoker和所有派生的调用者。访问者模式很好地实现了面向对象设计的开放/封闭原则:系统应该向扩展开放,不受修改。
(在传统的访问者模式中,Invoker将被Visitor代替,doTheJob()将被visit()取代,但对我来说,这些名称实际上并不能反映这样一个事实,即某些元素的工作已经完成。)
https://stackoverflow.com/questions/50387849
复制相似问题