Паттерн Посетитель на C++
Посетитель – это поведенческий паттерн, который позволяет добавить новую операцию для целой иерархии классов, не изменяя код этих классов.
Подробнее о том, почему Посетитель нельзя заменить простой перегрузкой методов читайте в статье Паттерн Посетитель и двойная диспетчеризация.
Особенности паттерна на C++
Сложность:
Популярность:
Применимость: Посетитель нечасто встречается в C++ коде из-за своей сложности и нюансов реализазации.
Концептуальный пример
Этот пример показывает структуру паттерна Посетитель, а именно – из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
main.cc: Пример структуры паттерна
/**
* Интерфейс Посетителя объявляет набор методов посещения, соответствующих
* классам компонентов. Сигнатура метода посещения позволяет посетителю
* определить конкретный класс компонента, с которым он имеет дело.
*/
class ConcreteComponentA;
class ConcreteComponentB;
class Visitor {
public:
virtual void VisitConcreteComponentA(const ConcreteComponentA *element) const = 0;
virtual void VisitConcreteComponentB(const ConcreteComponentB *element) const = 0;
};
/**
* Интерфейс Компонента объявляет метод accept, который в качестве аргумента
* может получать любой объект, реализующий интерфейс посетителя.
*/
class Component {
public:
virtual ~Component() {}
virtual void Accept(Visitor *visitor) const = 0;
};
/**
* Каждый Конкретный Компонент должен реализовать метод accept таким образом,
* чтобы он вызывал метод посетителя, соответствующий классу компонента.
*/
class ConcreteComponentA : public Component {
/**
* Обратите внимание, мы вызываем visitConcreteComponentA, что соответствует
* названию текущего класса. Таким образом мы позволяем посетителю узнать, с
* каким классом компонента он работает.
*/
public:
void Accept(Visitor *visitor) const override {
visitor->VisitConcreteComponentA(this);
}
/**
* Конкретные Компоненты могут иметь особые методы, не объявленные в их
* базовом классе или интерфейсе. Посетитель всё же может использовать эти
* методы, поскольку он знает о конкретном классе компонента.
*/
std::string ExclusiveMethodOfConcreteComponentA() const {
return "A";
}
};
class ConcreteComponentB : public Component {
/**
* То же самое здесь: visitConcreteComponentB => ConcreteComponentB
*/
public:
void Accept(Visitor *visitor) const override {
visitor->VisitConcreteComponentB(this);
}
std::string SpecialMethodOfConcreteComponentB() const {
return "B";
}
};
/**
* Конкретные Посетители реализуют несколько версий одного и того же алгоритма,
* которые могут работать со всеми классами конкретных компонентов.
*
* Максимальную выгоду от паттерна Посетитель вы почувствуете, используя его со
* сложной структурой объектов, такой как дерево Компоновщика. В этом случае
* было бы полезно хранить некоторое промежуточное состояние алгоритма при
* выполнении методов посетителя над различными объектами структуры.
*/
class ConcreteVisitor1 : public Visitor {
public:
void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor1\n";
}
void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor1\n";
}
};
class ConcreteVisitor2 : public Visitor {
public:
void VisitConcreteComponentA(const ConcreteComponentA *element) const override {
std::cout << element->ExclusiveMethodOfConcreteComponentA() << " + ConcreteVisitor2\n";
}
void VisitConcreteComponentB(const ConcreteComponentB *element) const override {
std::cout << element->SpecialMethodOfConcreteComponentB() << " + ConcreteVisitor2\n";
}
};
/**
* Клиентский код может выполнять операции посетителя над любым набором
* элементов, не выясняя их конкретных классов. Операция принятия направляет
* вызов к соответствующей операции в объекте посетителя.
*/
void ClientCode(std::array<const Component *, 2> components, Visitor *visitor) {
// ...
for (const Component *comp : components) {
comp->Accept(visitor);
}
// ...
}
int main() {
std::array<const Component *, 2> components = {new ConcreteComponentA, new ConcreteComponentB};
std::cout << "The client code works with all visitors via the base Visitor interface:\n";
ConcreteVisitor1 *visitor1 = new ConcreteVisitor1;
ClientCode(components, visitor1);
std::cout << "\n";
std::cout << "It allows the same client code to work with different types of visitors:\n";
ConcreteVisitor2 *visitor2 = new ConcreteVisitor2;
ClientCode(components, visitor2);
for (const Component *comp : components) {
delete comp;
}
delete visitor1;
delete visitor2;
return 0;
}
Output.txt: Результат выполнения
The client code works with all visitors via the base Visitor interface:
A + ConcreteVisitor1
B + ConcreteVisitor1
It allows the same client code to work with different types of visitors:
A + ConcreteVisitor2
B + ConcreteVisitor2