← Назад к вопросам
Что делает паттерн Visitor?
1.7 Middle🔥 151 комментариев
#ООП и проектирование#Язык C++
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерн Visitor: назначение и применение
Visitor — это поведенческий паттерн проектирования, который решает классическую проблему: как добавить новую операцию к иерархии классов, не изменяя сами классы.
Основная идея
Вместо того чтобы писать операции внутри каждого класса иерархии, мы выносим логику операций в отдельные классы (Visitors). Это позволяет:
- Соблюдать принцип Single Responsibility
- Избежать множественных условных операторов
- Легко добавлять новые операции без изменения существующего кода
Проблема, которую решает Visitor
// ❌ Плохо — логика операций размазана по классам
class Shape {
virtual void draw() = 0;
virtual void save() = 0;
virtual void validate() = 0;
virtual void serialize() = 0;
// Каждая операция требует изменения всей иерархии!
};
class Circle : public Shape {
void draw() override { /* ... */ }
void save() override { /* ... */ }
void validate() override { /* ... */ }
void serialize() override { /* ... */ }
};
Решение с паттерном Visitor
// ✅ Хорошо — операции отделены от структуры
class Visitor {
public:
virtual ~Visitor() = default;
virtual void visit(Circle* circle) = 0;
virtual void visit(Rectangle* rect) = 0;
virtual void visit(Triangle* tri) = 0;
};
// Иерархия Figure остаётся простой и стабильной
class Figure {
public:
virtual ~Figure() = default;
virtual void accept(Visitor* visitor) = 0; // Dual dispatch
};
class Circle : public Figure {
public:
void accept(Visitor* visitor) override {
visitor->visit(this); // Вызывает visit(Circle*)
}
double radius = 5.0;
};
class Rectangle : public Figure {
public:
void accept(Visitor* visitor) override {
visitor->visit(this); // Вызывает visit(Rectangle*)
}
double width = 10.0, height = 20.0;
};
Конкретные Visitors
// Visitor для отрисовки
class DrawVisitor : public Visitor {
public:
void visit(Circle* circle) override {
std::cout << "Drawing circle with radius " << circle->radius << std::endl;
}
void visit(Rectangle* rect) override {
std::cout << "Drawing rectangle " << rect->width << "x" << rect->height << std::endl;
}
void visit(Triangle* tri) override {
std::cout << "Drawing triangle" << std::endl;
}
};
// Visitor для вычисления площади
class AreaVisitor : public Visitor {
public:
double area = 0.0;
void visit(Circle* circle) override {
area = 3.14159 * circle->radius * circle->radius;
}
void visit(Rectangle* rect) override {
area = rect->width * rect->height;
}
void visit(Triangle* tri) override {
area = 0.0; // Упрощённо
}
};
// Visitor для сохранения в БД
class SerializeVisitor : public Visitor {
public:
void visit(Circle* circle) override {
database.insert("circles", {"radius", circle->radius});
}
void visit(Rectangle* rect) override {
database.insert("rectangles", {"width", rect->width}, {"height", rect->height});
}
void visit(Triangle* tri) override {
// Сохранение треугольника
}
};
Использование
int main() {
std::vector<std::unique_ptr<Figure>> figures;
figures.push_back(std::make_unique<Circle>());
figures.push_back(std::make_unique<Rectangle>());
figures.push_back(std::make_unique<Triangle>());
// Добавляем новую операцию БЕЗ изменения Figure, Circle и т.д.
DrawVisitor draw_visitor;
for (auto& fig : figures) {
fig->accept(&draw_visitor); // Полиморфизм!
}
// Ещё одна операция
AreaVisitor area_visitor;
for (auto& fig : figures) {
fig->accept(&area_visitor);
}
std::cout << "Total area: " << area_visitor.area << std::endl;
}
Реальные примеры в production
1. Обход и валидация AST в компиляторе
class ASTVisitor {
public:
virtual void visit(BinaryExprNode* node) = 0;
virtual void visit(FunctionCallNode* node) = 0;
virtual void visit(VariableNode* node) = 0;
};
class TypeCheckVisitor : public ASTVisitor {
void visit(BinaryExprNode* node) override {
// Проверить типы операндов
validateTypes(node->left, node->right);
}
};
class CodeGenVisitor : public ASTVisitor {
void visit(BinaryExprNode* node) override {
// Сгенерировать machine code
emitAdd(node->left, node->right);
}
};
2. Обработка различных типов XML элементов
class XMLElement {
public:
virtual void accept(XMLVisitor* visitor) = 0;
};
class XMLTag : public XMLElement {
void accept(XMLVisitor* visitor) override { visitor->visit(this); }
};
class XMLVisitor {
public:
virtual void visit(XMLTag* tag) = 0;
virtual void visit(XMLAttribute* attr) = 0;
virtual void visit(XMLText* text) = 0;
};
Преимущества и недостатки
Преимущества
- Open/Closed Principle — открыто для расширения (новые Visitors), закрыто для модификации (Figure, Circle)
- Separation of Concerns — логика операции отделена от структуры данных
- Легко добавлять операции — просто создаём новый Visitor
- Централизация логики — вся логика одной операции в одном месте
Недостатки
- Сложность — требует понимания double dispatch
- Хрупкость иерархии — добавление новой фигуры требует обновления всех Visitors
- Производительность — виртуальные вызовы имеют overhead
- Скрывает логику — операции не очевидны из класса Figure
Когда использовать Visitor
✅ Используйте когда:
- Часто добавляются новые операции к существующей структуре
- Операции логически независимы
- Иерархия классов стабильна
- Нужна типизированная обработка разных элементов
❌ Избегайте когда:
- Иерархия часто меняется
- Мало операций
- Нужна максимальная производительность
- Команда не знакома с паттерном
Альтернативы
- Если иерархия нестабильна — используйте полиморфные методы в классах
- Если нужны только 1-2 операции — просто добавьте методы
- В современном C++ — рассмотрите std::variant с std::visit (более типобезопасно)