访问者模式C++
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不修改集合元素类的前提下,为集合中的元素添加新的操作。这种模式将操作与元素的结构分离,使操作可以独立变化。
访问者模式的核心角色
- Visitor(访问者接口):声明对每个元素类的访问操作
- ConcreteVisitor(具体访问者):实现访问者接口,定义对每个元素的具体操作
- Element(元素接口):声明接受访问者的接口(
accept()
方法) - ConcreteElement(具体元素):实现元素接口,通过
accept()
方法接收访问者 - ObjectStructure(对象结构):包含元素集合,提供遍历元素的方法
C++实现示例
以下以"企业员工数据统计"为例实现访问者模式,统计不同类型员工(工程师、经理)的工作时长、项目数量等信息:
#include <iostream>
#include <string>
#include <vector>
#include <memory>// 前向声明
class Engineer;
class Manager;// 访问者接口
class Visitor {
public:virtual ~Visitor() = default;virtual void visit(Engineer* engineer) = 0; // 访问工程师virtual void visit(Manager* manager) = 0; // 访问经理
};// 元素接口:员工
class Employee {
protected:std::string name; // 姓名int id; // 工号public:Employee(std::string n, int i) : name(std::move(n)), id(i) {}virtual ~Employee() = default;const std::string& getName() const { return name; }int getId() const { return id; }// 接受访问者virtual void accept(Visitor* visitor) = 0;
};// 具体元素1:工程师
class Engineer : public Employee {
private:int workHours; // 工作时长(小时)int projectCount; // 参与项目数public:Engineer(std::string n, int i, int hours, int projects): Employee(std::move(n), i), workHours(hours), projectCount(projects) {}int getWorkHours() const { return workHours; }int getProjectCount() const { return projectCount; }// 接受访问者,调用访问者的对应方法void accept(Visitor* visitor) override {visitor->visit(this);}
};// 具体元素2:经理
class Manager : public Employee {
private:int teamSize; // 团队规模int managedProjects; // 管理项目数double budget; // 管理预算(万元)public:Manager(std::string n, int i, int size, int projects, double budg): Employee(std::move(n), i), teamSize(size), managedProjects(projects), budget(budg) {}int getTeamSize() const { return teamSize; }int getManagedProjects() const { return managedProjects; }double getBudget() const { return budget; }// 接受访问者,调用访问者的对应方法void accept(Visitor* visitor) override {visitor->visit(this);}
};// 具体访问者1:工作统计访问者
class WorkStatsVisitor : public Visitor {
private:int totalWorkHours = 0; // 总工作时长int totalProjects = 0; // 总项目数int engineerCount = 0; // 工程师数量int managerCount = 0; // 经理数量public:void visit(Engineer* engineer) override {engineerCount++;totalWorkHours += engineer->getWorkHours();totalProjects += engineer->getProjectCount();std::cout << "统计工程师: " << engineer->getName() << ", 工作时长: " << engineer->getWorkHours() << ", 参与项目: " << engineer->getProjectCount() << std::endl;}void visit(Manager* manager) override {managerCount++;totalProjects += manager->getManagedProjects();std::cout << "统计经理: " << manager->getName() << ", 管理项目: " << manager->getManagedProjects() << ", 团队规模: " << manager->getTeamSize() << std::endl;}// 显示统计结果void showStats() const {std::cout << "\n===== 工作统计结果 =====" << std::endl;std::cout << "工程师总数: " << engineerCount << std::endl;std::cout << "经理总数: " << managerCount << std::endl;std::cout << "总工作时长: " << totalWorkHours << "小时" << std::endl;std::cout << "总项目数: " << totalProjects << std::endl;}
};// 具体访问者2:成本统计访问者
class CostVisitor : public Visitor {
private:double totalSalary = 0.0; // 总薪资double totalBudget = 0.0; // 总预算public:void visit(Engineer* engineer) override {// 假设工程师时薪100元double salary = engineer->getWorkHours() * 100;totalSalary += salary;std::cout << "计算工程师 " << engineer->getName() << " 薪资: " << salary << "元" << std::endl;}void visit(Manager* manager) override {// 假设经理月薪固定20000元 + 预算10%double salary = 20000 + manager->getBudget() * 10000 * 0.1;totalSalary += salary;totalBudget += manager->getBudget();std::cout << "计算经理 " << manager->getName() << " 薪资: " << salary << "元, 管理预算: " << manager->getBudget() << "万元" << std::endl;}// 显示成本统计结果void showCosts() const {std::cout << "\n===== 成本统计结果 =====" << std::endl;std::cout << "总薪资支出: " << totalSalary << "元" << std::endl;std::cout << "总管理预算: " << totalBudget << "万元" << std::endl;}
};// 对象结构:员工集合
class EmployeeList {
private:std::vector<std::unique_ptr<Employee>> employees;public:// 添加员工void addEmployee(std::unique_ptr<Employee> employee) {employees.push_back(std::move(employee));}// 接受访问者,让所有员工被访问void accept(Visitor* visitor) {for (const auto& emp : employees) {emp->accept(visitor);}}
};// 客户端代码
int main() {// 创建员工集合EmployeeList company;// 添加员工company.addEmployee(std::make_unique<Engineer>("张三", 1001, 160, 3));company.addEmployee(std::make_unique<Engineer>("李四", 1002, 180, 2));company.addEmployee(std::make_unique<Manager>("王五", 2001, 8, 5, 500));company.addEmployee(std::make_unique<Engineer>("赵六", 1003, 150, 4));company.addEmployee(std::make_unique<Manager>("钱七", 2002, 12, 3, 800));// 工作统计WorkStatsVisitor workStats;std::cout << "=== 工作统计 ===" << std::endl;company.accept(&workStats);workStats.showStats();// 成本统计CostVisitor costStats;std::cout << "\n=== 成本统计 ===" << std::endl;company.accept(&costStats);costStats.showCosts();return 0;
}
代码解析
-
访问者接口(
Visitor
):- 声明了对每种具体元素(
Engineer
、Manager
)的访问方法 - 新增加的操作只需实现这个接口,无需修改元素类
- 声明了对每种具体元素(
-
元素接口(
Employee
):- 声明了接受访问者的
accept()
方法 - 所有具体元素都必须实现这个方法,将自身传递给访问者
- 声明了接受访问者的
-
具体元素:
Engineer
和Manager
分别实现了accept()
方法,调用访问者对应的visit()
方法- 元素类只包含自身的数据和基本行为,不包含统计等业务操作
-
具体访问者:
WorkStatsVisitor
实现了工作时长和项目数量的统计CostVisitor
实现了薪资和预算的统计- 每个访问者专注于一类操作,便于扩展新的统计维度
-
对象结构(
EmployeeList
):- 维护员工集合,提供
accept()
方法让所有员工被访问者访问 - 简化了客户端代码,无需逐个遍历元素
- 维护员工集合,提供
访问者模式的优缺点
优点:
- 分离了数据结构与操作,新增操作只需添加新的访问者,无需修改元素类
- 集中管理相关操作,同一类操作被封装在一个访问者中
- 便于为不同类型的元素提供不同的操作
- 可以在不修改元素的情况下,对元素进行复杂的统计或转换
缺点:
- 增加新元素类型时,需要修改所有访问者接口和实现,违反开放-封闭原则
- 元素类必须暴露内部状态给访问者,可能破坏封装性
- 访问者与元素之间存在紧密耦合,增加了系统复杂度
适用场景
- 当需要对一个包含多种类型对象的集合进行多种不相关的操作时
- 当需要为现有元素添加新操作,且不希望修改元素类时
- 当元素类相对稳定,而操作经常变化或增加时
常见应用:
- 数据报表生成(对同一组数据生成不同报表)
- 编译器的语法树分析(不同阶段对语法树进行不同处理)
- 复杂集合的批量操作(如过滤、转换、统计等)
- 文档解析器(对不同类型的文档元素进行不同处理)
访问者模式的关键是双分派技术:元素的accept()
方法调用访问者的visit()
方法,而visit()
方法又使用元素的具体类型,从而实现对不同元素执行不同操作。