访问者模式和策略模式、命令模式有什么区别?
简化版
策略模式封装可替换算法,关注“同一任务的不同算法”;命令模式封装请求,关注“请求对象化、排队、撤销”;访问者模式封装作用在对象结构上的操作,关注“稳定元素结构上新增多种操作”。
详细版
三者区别:
- 策略模式:把算法族封装起来,运行时选择一种算法。
- 命令模式:把一次请求封装成对象,支持执行、撤销、队列、日志。
- 访问者模式:把对不同元素类型的操作封装成访问者。
判断方式:
- 如果变化的是算法,例如不同折扣算法,用策略。
- 如果变化的是请求动作,例如复制、粘贴、撤销,用命令。
- 如果变化的是对一组对象结构的操作,例如导出、统计、校验,用访问者。
访问者模式通常需要元素提供 accept,策略和命令一般不要求被处理对象配合这种双分派结构。
完整版教学
一、策略模式关注算法替换
策略模式解决的是“同一个问题有多种做法”。例如支付计费、排序算法、促销折扣、路由选择。
上下文对象持有一个策略,运行时切换策略即可。策略通常处理同一种输入输出,不强调元素类型层级。
二、命令模式关注请求对象化
命令模式把动作封装成对象。命令对象通常包含执行者、参数和执行方法。
它适合撤销重做、任务队列、异步执行、宏命令、操作日志。例如编辑器的复制、粘贴、撤销,或者消息队列中的任务。
命令模式关心“动作如何被保存、执行、撤销”,不是关心对象结构上的多类型访问。
三、访问者模式关注对象结构操作
访问者模式的重点是:有一组不同类型元素,对它们要新增很多操作。
它需要元素参与分派,通过 accept 把元素真实类型交给访问者。它解决的是“操作维度扩展”。
四、常见误区与工程判断
面试官喜欢给一个需求让你选模式。例如“订单有多种优惠计算方式”,这更像策略;“把用户操作记录下来支持撤销”,这更像命令;“对语法树做类型检查、代码生成、格式化”,这更像访问者。
工程中不要只看类名,要看变化点。设计模式本质是在管理变化,变化点判断对了,模式才有意义。
五、三者最容易混淆的边界
访问者、策略、命令都能把行为封装成对象,但封装的维度不同。策略模式封装的是“同一个算法点的不同实现”,比如不同折扣算法;命令模式封装的是“一个可执行请求”,强调执行、撤销、排队、日志;访问者模式封装的是“作用在一组不同元素类型上的一整套操作”。
如果面试官给你一个场景:同一订单要选择不同计价规则,更像策略;用户点击按钮后要记录操作并支持撤销,更像命令;一组稳定对象要新增导出、统计、审计逻辑,更像访问者。不要只看“都有接口和实现类”,要看行为绑定在哪个变化点上。
回答时可以主动说:访问者通常需要元素配合 accept,策略一般由上下文选择算法,命令通常有 receiver 或执行目标。调用结构不同,说明它们解决的问题也不同。
还可以从对象协作方式进一步区分:策略通常不要求被处理对象实现 accept;命令通常关心 execute、undo、队列和日志;访问者则要求元素主动接收访问者,并把自身类型交给 visitor。协作结构不同,是判断模式的硬证据。
六、用工程约束检验答案
策略替换一个算法,命令封装一次请求,访问者为一组异构元素定义操作矩阵。若有 3 种折扣算法选 1 种是策略;把“退款”排队和重试是命令;让审计逻辑分别处理员工、部门、项目才是访问者。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| Strategy | 可替换算法族 | Context 选择一种算法 |
| Command | 请求对象化 | 支持队列、日志、撤销 |
| Visitor | 异构元素上的横向操作 | accept + visit 双分派 |
把关键关系压缩成一条可复述的路径:
算法可切换 -> Strategy
请求要排队/撤销 -> Command
稳定元素族新增操作 -> Visitor
先识别问题,再选结构
三者都封装行为,但封装对象、调用链和变化轴不同,不能因为“把逻辑放进类”就混为一谈。
落地前可以再按下面 3 步复核:
- 先说明“Strategy”的核心机制:可替换算法族;再交代边界:Context 选择一种算法。
- 接着分析“Command”:请求对象化;不能遗漏对应代价或结果:支持队列、日志、撤销。
- 最后用“Visitor”检查方案:异构元素上的横向操作;验收时确认accept + visit 双分派。
这三项构成完整判断链:先讲清Strategy,再说明Command,最后用Visitor检验实现是否越界。
面试中若能给出违反“accept + visit 双分派”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“Strategy”就认为方案成立。 必须同时说明核心机制“可替换算法族”和工程边界“Context 选择一种算法”。
- 误区:把“Command”当成无条件结论。 只有在“请求对象化”成立时,才能据此讨论“支持队列、日志、撤销”。
- 追问:访问者能在运行时切换吗? 能传入不同访问者,但这不等于它的核心问题是算法替换。
- 追问:命令也能撤销,和访问者有关吗? 命令围绕请求及逆操作,访问者围绕元素类型分派。
- 追问:策略需要 accept 吗? 不需要,Context 通常直接调用统一策略接口。
- 追问:命令能访问多种接收者吗? 能,但它封装的是一次请求,不要求为每种元素声明 visit 重载。
- 追问:何时可能组合? 命令可携带一个访问者,对对象结构执行可排队的批量操作。
八、加强记忆
记忆时抓住这条主线:策略封装算法;命令封装请求;访问者封装对象结构上的操作;选模式先看变化点,不看名字。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。