Почему нам нужно принять () в шаблоне Visitor и почему мы не можем вызвать visitor.visit () напрямую?

Я пересматриваю шаблон Visitor, который использовал некоторое время назад. У нас есть базовый класс 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.visit (элемент)? Это делает использование шаблона Visitor громоздким и требует дополнительного кода во всей иерархии классов Element.

Так может кто-нибудь объяснить преимущества accept () здесь?

0

Решение

Я долго путался с моделью посетителя, и я пытался найти объяснения по всему Интернету, и эти объяснения смутили меня еще больше. Теперь я понял причины, по которым нужен шаблон Visitor и как он реализован, вот он:

Шаблон посетителя необходим для решения проблемы двойной отправки.

Одиночная отправка — когда у вас есть одна иерархия классов, и у вас есть экземпляр конкретного класса в этой иерархии
и вы хотите вызвать соответствующий метод для этого экземпляра. Это решается с помощью переопределения функций (с использованием таблицы виртуальных функций в C ++).

Двойная отправка, когда у вас есть две иерархии классов и у тебя есть один экземпляр конкретного класса из одной иерархии и один экземпляр конкретного класса из другой иерархии и вы хотите вызвать соответствующий метод, который будет выполнять работу для этих двух конкретных экземпляров.

Давайте посмотрим на пример.

Первоклассная иерархия: животные. База: Animal, получено: Fish, Mammal, Bird,
Иерархия второго класса: призыватели. База: Invoker, получено: MovementInvoker (переместить животное), VoiceInvoker (заставляет животное звучать), FeedingInvoker (кормит животное).

Теперь для каждого конкретного животного и каждого конкретного призывателя мы хотим, чтобы была вызвана только одна конкретная функция, которая будет выполнять определенную работу (например, кормить птицу или звучать рыбой). Таким образом, в целом у нас есть 3×3 = 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 ++):

visitor.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.accept(invoker)?

Предположим, что экземпляр Animal является Bird и случай Invoker является FeedingInvoker,

Тогда благодаря виртуальной таблице функций Bird::accept(Invoker&) будет вызван, который в свою очередь будет работать invoker.doTheJob(Bird&),
Как Invoker пример FeedingInvoker, таблица виртуальных функций будет использовать FeedingInvoker::accept(Bird&) для этого звонка.

Таким образом, мы сделали двойную отправку и вызвали правильный метод (один из 9 возможных методов) для Bird а также FeedingInvoker,

Почему шаблон посетителя хорош?

  1. Клиенту не нужно зависеть как от сложных иерархий классов Animals, так и Invokers.

  2. Если новое конкретное животное (скажем, Insect) необходимо добавить, не существует Animal иерархия должна быть изменена.
    Нам нужно только добавить: doTheJob(Insect& insect) в Invoker и все производные invokers.

Шаблон посетителя элегантно реализует принцип открытого / закрытого объектно-ориентированного проектирования: система должна быть открыта для расширений и закрыта для изменений.

(В классической схеме Visitor Invoker будет заменен на Visitor а также doTheJob() от visit(), но для меня эти имена на самом деле не отражают тот факт, что некоторая работа выполняется над элементами).

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]