Объяснение шаблона посетителя
Итак, я прочитал всю документацию о шаблоне посетителя, и я все еще сильно запутался. Я взял этот пример из другого вопроса, может ли кто-нибудь помочь мне понять? Например, когда мы используем шаблон дизайна посетителя? Я думаю, что я, возможно, понял некоторые из них, но я просто не могу видеть большую картину. Как узнать, когда я могу его использовать?
class equipmentVisited
{
virtual void accept(equipmentVisitor* visitor) = 0;
}
class floppyDisk : public equipmentVisited
{
virtual void accept(equipmentVisitor* visitor);
}
class processor : public equipmentVisited
{
virtual void accept(equipmentVisitor* visitor);
}
class computer : public equipmentVisited
{
virtual void accept(equipmentVisitor* visitor);
}
class equipmentVisitor
{
virtual void visitFloppyDisk(floppyDisk* );
virtual void visitProcessor(processor* );
virtual void visitComputer(computer* );
}
// Some additional classes inheriting from equipmentVisitor would be here
equipmentVisited* visited;
equipmentVisitor* visitor;
// Here you initialise visited and visitor in any convenient way
visited->accept(visitor);
Ответы
Ответ 1
Шаблон посетителя используется для реализации двойной отправки. Простыми словами это означает, что исполняемый код зависит от типов среды выполнения двух объектов.
Когда вы вызываете обычную виртуальную функцию, это отдельная отправка: кусок кода, который запускается, зависит от типа среды выполнения одного объекта, а именно от того, какой виртуальный метод вы вызываете.
С шаблоном посетителя метод, который вызывается в конечном счете, зависит от типа двух объектов - типа объекта, реализующего equipmentVisitor
, и типа объекта, на который вы вызываете accept
(т.е. equipmentVisited
).
Существуют другие способы реализации двойной отправки на С++. Пункт 31 Скотта Мейера "Более эффективный С++" глубоко изучает этот вопрос.
Ответ 2
Я думаю, что имя шаблона Visitor довольно неудачно.
Вместо слова "посетитель" я бы сказал "Functor" или "Operator", а вместо "visit" я бы сказал "apply".
Мое понимание шаблона посетителя выглядит следующим образом:
В метапрограмме шаблонов (STL/BOOST) (привязка времени компиляции) вы можете достичь (ортогональный дизайн)
разделение операций от структур, с помощью функциональных объектов (Функторы.)
Например, в
template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
comp является функтором/оператором, представляющим операцию "меньше" в очень общем виде, поэтому вам не нужно иметь много вариантов функции сортировки:
Для шаблона посетителя вы хотите добиться чего-то подобного, но в случае времени выполнения (позднего) привязки:
Вы хотите упростить интерфейс A, вы хотите сохранить возможность для будущих расширений (новые операции, работающие с A), и вы хотите добиться стабильности интерфейса A в случае этих расширений.
Из исходного "жирного" класса:
class A
{
public:
virtual void function_or_operation_1();//this can be implemented in terms of public interface of the other functions
virtual void function_or_operation_2();
//..etc
virtual void function_or_operation_N();
public:
//stable public interface, some functions of procedures
private:
//....
}
вы удаляете как можно больше функций из открытого интерфейса (если они могут быть реализованы с точки зрения неисследованных функций одного и того же публичного интерфейса)
и представляют операции как объекты-объекты-объекты или объекты из новой иерархии Functor:
Вы уменьшаете количество функций в базовом классе A, имея очень общий интерфейс, используя forward объявленный Functor_or_Operator:
class Functor_or_Operator;
class A
{
public:
virtual void apply(Functor_or_Operator*);//some generic function operates on this objects from A hierarchy
//..etc
public:
//stable public interface, some functions
private:
//....
}
//Теперь у вас есть классы N (= 3) в иерархии A (A, B, C) и M или функции, представленные классами в иерархии Functor_or_Operator
Вам нужно реализовать определения N * M о том, как каждая операция из Functor_or_Operator работает над каждым классом в иерархии A.
Самое главное, вы можете это сделать, не меняя интерфейса класса "А".
Объявление класса "А" становится очень стабильным в случае новых дополнений при введении новых операций или функций, работающих с объектами иерархии А
или в случае новых производных классов в иерархии А.
Стабильность A (без изменений в A) при наличии дополнений важна, чтобы избежать дорогостоящей (а иногда и невозможной) перекомпиляции программного обеспечения, которое включает заголовки A во многих местах.
Для каждого нового класса в иерархии A вы расширяете определение базы Functor_or_Operator, добавляете новые файлы реализации, но вам не нужно касаться заголовка базового класса A (обычно интерфейса или абстрактного класса).
class Functor_or_Operator
{
virtual void apply(A*)=0;
virtual void apply(B*)=0;
virtual void apply(C*)=0;
}
void A::apply(Functor_or_Operator* f)
{ f->apply(this);} //you need this only if A is not abstract (it is instantiable)
class B:public A
{
public:
void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymhorphic Functor f on this object
//..the rest of B implementation.
}
class C:public A
{
public:
void apply(Functor_or_Operator* f) { f->apply(this);} //dynamic dispatch , you call polymorfic Functor f on this object
//..the rest of C implementation.
}
class Functor_or_Operator_1:public Functor_or_Operator
{
public:
//implementations of application of a function represented by Functor_or_Operator_1 on each A,B,C
void apply(A*) {}//( only if A is instantiable,not an abstract class)
void apply(B*) {}
void apply(C*) {}
}
class Functor_or_Operator_2:public Functor_or_Operator
{
public:
//implementations of application of a function represented by Functor_or_Operator_2 on each A,B,C
void apply(A*) {}//( only if A is instantiable,not an abstract class)
void apply(B*) {}
void apply(C*) {}
}