从C++编程入手设计模式——访问者模式
在软件开发中,我们常常会面对这样的场景:某一组对象是稳定的,但我们希望对它们执行不同的操作,比如打印信息、计算总和、导出数据等。访问者模式就是为了解决这种需求而出现的。
访问者模式的核心思想是把操作从对象本身分离出去。对象只负责结构,而访问者负责逻辑。这样,当我们需要添加新的操作时,只需要新增一个访问者类,而不需要去改动原有的对象定义。这是一种典型的“对修改关闭,对扩展开放”的设计方式。
我们以一个简单的图形示例说明访问者的基本组成:
[Circle] [Rectangle] [Triangle]| | || accept(visitor) | accept(visitor) | accept(visitor)↓ ↓ ↓visitor.visit(this) visitor.visit(this) visitor.visit(this)↓↓↓[AreaCalculator]
每个图形只需要调用 accept(visitor)
,然后让访问者自己决定要对该图形干什么。图形不关心访问者的行为,访问者也不关心图形的其他内部结构,它只处理它理解的部分。
访问者与其他模式的对比
一些同志会把访问者模式和策略模式或命令模式搞混,下面我们做一些简要的比较。
与策略模式的对比
策略模式是“对象自己决定调用哪个策略”。例如一个排序器对象,它可以选择使用冒泡或快排。这个行为是封装在策略对象里的,由调用者自己选择。
访问者模式则是“结构体让外部访问者决定行为”。对象不会自己决定使用什么逻辑,它只是说“欢迎你来访问我”。
比较项 | 策略模式 | 访问者模式 |
---|---|---|
核心目标 | 动态切换算法或行为 | 抽离并扩展对象结构的操作逻辑 |
典型行为发起者 | 客户端 | 被访问的对象结构 |
对象结构变化 | 支持 | 不支持(结构变更需改全部访问者) |
与命令模式的对比
命令模式是把动作抽象为命令对象,比如“打印”、“撤销”,客户端持有命令并在合适的时候执行。
访问者模式则关注于遍历对象结构,执行与对象类型强相关的逻辑。一个命令可以作用于很多对象;而访问者是在遍历结构时,进行特定逻辑处理。
C++ 中的双重分派机制
访问者模式有一个典型的特性叫“双重分派”(Double Dispatch)。通俗来说,它的流程是这样的:
shape->accept(visitor);
↓
visitor.visit(*this);
第一次是形如:
void Circle::accept(ShapeVisitor& v) { v.visit(*this); }
而访问者又定义了多个重载:
void visit(Circle& c);
void visit(Rectangle& r);
void visit(Triangle& t);
在 C++ 中,我们通过虚函数机制实现第一次分派,通过函数重载 + 类型引用来实现第二次分派。这种机制看起来有点拐弯,但正因为如此,访问者才能根据运行时对象的真实类型执行不同逻辑。
访问者变体设计
访问者模式本身也有一些典型的变种设计,下面列出几个常见场景。
1. 返回值访问者
我们可以让访问者返回值,例如在计算表达式时返回结果:
struct Evaluator : ExprVisitor {int result = 0;void visit(AddExpr& e) override {e.left->accept(*this);int l = result;e.right->accept(*this);int r = result;result = l + r;}
};
每一步通过成员变量 result
暂存递归过程的值。
2. 多访问者组合
有时我们需要多个访问者协作,例如一个打印器 + 日志器 + 导出器,可以统一封装为“访问者链”,逐个调用:
[Shape]↓
[Printer] -> [Logger] -> [Exporter]
这类似于责任链的组合,用于把多种行为分开而不耦合。
适用场景总结
访问者模式适用于以下几种场景:
- 数据结构稳定,行为变化频繁。
- 需要对对象结构进行“集中式处理”,比如统一导出。
- 需要多个逻辑处理过程而不破坏类本身。
常见应用场景包括:
- 编译器中的 AST(抽象语法树)处理器
- 报表生成系统(访问各类统计数据)
- 文件结构扫描器(文件夹、文件的遍历)
- UI 渲染树分析器
练习题:几何图形面积计算器
题目描述:
定义一组几何图形(如:圆形 Circle
、矩形 Rectangle
、三角形 Triangle
),使用访问者模式实现一个面积计算器,使得添加新的图形时无需修改已有的访问者逻辑。
要求:
- 所有图形类实现一个抽象基类
Shape
,具有accept(Visitor&)
方法。 - 定义一个
ShapeVisitor
抽象类,并由其派生AreaCalculatorVisitor
实现面积计算逻辑。 - 添加一个主函数中构造多个图形并调用访问者进行面积统计。