2

我正在修改前一段时间使用的访客模式。我们有一个基类 Element,它有虚方法 accept(Visitor) 并且这个方法在所有继承自 Element 的类中被覆盖。在任何派生类中,accept() 所做的只是调用visitor->visit(*this)。现在,当客户端运行代码时,他/她会执行以下操作:

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

为什么客户不能像这样调用visitor->visit(element):

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

调用 element.accept(visitor) 后又调用 visitor.visit(element) 有哪些有用的信息?这使得访问者模式的使用变得繁琐,并且在元素类的所有层次结构中都需要额外的代码。

那么有人可以在这里解释 accept() 的好处吗?

4

1 回答 1

4

长期以来,我一直对访问者模式感到困惑,并且一直试图在整个 Internet 上找到解释,而这些解释让我更加困惑。现在我明白了为什么需要访问者模式的原因以及它是如何实现的,所以这里是:

需要访问者模式来解决双重调度问题。

单一分派——当你有一个类层次结构并且你在这个层次结构中有一个具体类的实例并且你想为这个实例调用一个适当的方法。这可以通过函数覆盖来解决(使用 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.
        }
    }
}

现在:如何在 RUN-TIME 中调用 9 个(或其他)特定方法之一来处理这个特定的Animal和这个特定的Invoker

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

所以你需要调用一个虚方法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;
};

#endif

访客.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和一个Invokerand 调用时会发生animal.accept(invoker)什么呢?

假设is的实例和AnimalisBird的实例。InvokerFeedingInvoker

然后由于虚函数表Bird::accept(Invoker&)将被调用,该表将依次运行invoker.doTheJob(Bird&)。作为Invoker实例FeedingInvoker,虚函数表将FeedingInvoker::accept(Bird&)用于此调用。

Bird所以我们进行了双重调度,并为and调用了正确的方法(9 种可能的方法之一)FeedingInvoker

为什么访客模式好?

  1. 客户端不需要同时依赖 Animals 和 Invokers 的复杂类层次结构。

  2. Insect如果需要添加新的具体动物(例如),则无需Animal更改现有层次结构。我们只需要添加:doTheJob(Insect& insect)Invoker和所有派生的调用者。

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

(在经典的访问者模式Invoker中,将被替换为VisitordoTheJob()visit()但对我来说,这些名称实际上并不能反映在元素上完成了某些工作的事实)。

于 2018-05-24T12:28:08.673 回答