Ответ
Посетитель — это поведенческий паттерн, который позволяет добавлять новые операции к целым иерархиям классов, не изменяя сами эти классы. Он реализует принцип двойной диспетчеризации: операция выполняется в зависимости от типов как самого посетителя, так и посещаемого элемента.
Ключевая идея: Вы выносите набор родственных операций (например, рендеринг, экспорт, валидация) в отдельные классы-посетители. Каждый класс иерархии должен всего лишь один раз реализовать метод accept(Visitor&), который делегирует выполнение посетителю.
Пример: операции над геометрическими фигурами
#include <iostream>
#include <vector>
// Предварительные объявления и интерфейс Посетителя
class Circle;
class Rectangle;
class ShapeVisitor {
public:
virtual void visit(Circle& circle) = 0;
virtual void visit(Rectangle& rect) = 0;
virtual ~ShapeVisitor() = default;
};
// Интерфейс элемента иерархии
class Shape {
public:
virtual void accept(ShapeVisitor& visitor) = 0;
virtual ~Shape() = default;
};
// Конкретные классы иерархии
class Circle : public Shape {
public:
double radius;
Circle(double r) : radius(r) {}
void accept(ShapeVisitor& visitor) override { visitor.visit(*this); }
};
class Rectangle : public Shape {
public:
double width, height;
Rectangle(double w, double h) : width(w), height(h) {}
void accept(ShapeVisitor& visitor) override { visitor.visit(*this); }
};
// Конкретные посетители (новые операции)
class AreaCalculator : public ShapeVisitor {
public:
void visit(Circle& circle) override {
double area = 3.14159 * circle.radius * circle.radius;
std::cout << "Area of circle: " << area << 'n';
}
void visit(Rectangle& rect) override {
double area = rect.width * rect.height;
std::cout << "Area of rectangle: " << area << 'n';
}
};
class DrawVisitor : public ShapeVisitor {
public:
void visit(Circle& circle) override {
std::cout << "Drawing a circle with radius " << circle.radius << 'n';
}
void visit(Rectangle& rect) override {
std::cout << "Drawing a rectangle " << rect.width << "x" << rect.height << 'n';
}
};
int main() {
std::vector<Shape*> shapes = {new Circle(5.0), new Rectangle(3.0, 4.0)};
AreaCalculator areaCalc;
DrawRenderer drawRender;
for (auto shape : shapes) {
shape->accept(areaCalc); // Вычисляем площадь
shape->accept(drawRender); // Рисуем
}
// ... очистка памяти
return 0;
}
Когда использовать?
- Когда иерархия классов стабильна, но над ней нужно выполнять множество различных и часто меняющихся операций.
- Когда логика операции сильно зависит от конкретного типа класса.
Недостатки:
- Усложняет добавление новых классов в иерархию (придётся обновлять всех посетителей).
- Посетитель часто требует открытого доступа к полям класса, что может нарушать инкапсуляцию.