访问者模式和迭代器模式有什么区别?
简化版
迭代器模式关注如何顺序访问集合元素而不暴露内部结构;访问者模式关注对对象结构中的不同元素执行不同操作。迭代器解决遍历问题,访问者解决操作扩展问题,两者可以结合使用。
详细版
迭代器模式:
- 目标是遍历集合。
- 统一访问元素的方式。
- 不关心元素上执行什么复杂操作。
- 重点是
hasNext()、next()这类遍历接口。
访问者模式:
- 目标是扩展操作。
- 不同元素类型执行不同 visit 方法。
- 需要元素提供 accept。
- 重点是元素类型和访问者类型的双分派。
两者可以结合:迭代器负责遍历元素集合,访问者负责处理每个元素。
完整版教学
一、迭代器模式解决遍历封装
集合内部可能是数组、链表、树、图。客户端不应该知道它内部怎么存,只需要一个统一遍历接口。
迭代器模式把遍历状态封装起来,让客户端按统一方式访问元素。
二、访问者模式解决操作扩展
访问者模式不重点解决“怎么走到每个节点”,而是解决“走到节点后执行什么操作”。
例如 AST 可以用迭代器或递归遍历节点,但类型检查、代码生成、格式化这些操作更适合放到访问者里。
三、两者如何配合
对象结构可以用迭代器暴露遍历能力:
while (iterator.hasNext()) {
Element element = iterator.next();
element.accept(visitor);
}
这里迭代器负责“下一个是谁”,访问者负责“拿到之后怎么处理”。
四、常见误区与工程判断
如果一个需求只是“隐藏集合内部结构并遍历元素”,用迭代器即可。如果需求是“新增很多对不同元素的操作”,才考虑访问者。
不要因为访问者里也遍历对象结构,就认为它等于迭代器。访问者的核心不是遍历,而是把操作从元素类中分离出去。
五、遍历和操作要分开看
访问者和迭代器经常一起出现,但它们不是同一层问题。迭代器解决的是“如何按统一方式访问集合元素”,重点是遍历过程;访问者解决的是“对遍历到的不同元素执行什么操作”,重点是行为扩展。
在工程里,ObjectStructure 内部可能用迭代器遍历元素,然后对每个元素调用 accept(visitor)。这说明迭代器可以成为访问者流程中的一部分,但不能替代访问者。迭代器不知道元素类型对应哪些业务处理,也不负责把新增操作封装起来。
面试时可以这样区分:如果问题是屏蔽集合内部结构、统一遍历方式,考虑迭代器;如果问题是稳定元素集合上不断增加处理逻辑,考虑访问者。一个管“走到谁”,一个管“对谁做什么”。
如果面试官继续追问“访问者里是不是也要遍历”,可以承认很多实现确实会遍历,但遍历只是把元素交给访问者的过程,不是模式的核心收益。真正的收益在于同一批元素上可以挂不同访问者,分别完成统计、导出、校验等操作。
六、用工程约束检验答案
迭代器回答“下一个元素怎么拿”,访问者回答“拿到不同类型元素后做什么”。处理 1000 个树节点时,迭代器可以隐藏深度优先遍历,访问者则对文件、目录等具体节点执行统计;两者处在不同职责层。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| 迭代器 | 封装遍历顺序和游标 | 通常不关心具体业务操作 |
| 访问者 | 封装针对元素类型的操作 | 通常不负责选择下一个元素 |
| 组合使用 | Iterator 提供元素 | 每个元素 accept 同一 Visitor |
把关键关系压缩成一条可复述的路径:
Iterator.hasNext / next
-> Element
-> accept(Visitor)
-> 类型化业务处理
能一起使用的两个模式通常不是替代关系;回答时要分别指出遍历维度与操作维度。
落地前可以再按下面 3 步复核:
- 先说明“迭代器”的核心机制:封装遍历顺序和游标;再交代边界:通常不关心具体业务操作。
- 接着分析“访问者”:封装针对元素类型的操作;不能遗漏对应代价或结果:通常不负责选择下一个元素。
- 最后用“组合使用”检查方案:Iterator 提供元素;验收时确认每个元素 accept 同一 Visitor。
这三项构成完整判断链:先讲清迭代器,再说明访问者,最后用组合使用检验实现是否越界。
面试中若能给出违反“每个元素 accept 同一 Visitor”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“迭代器”就认为方案成立。 必须同时说明核心机制“封装遍历顺序和游标”和工程边界“通常不关心具体业务操作”。
- 误区:把“访问者”当成无条件结论。 只有在“封装针对元素类型的操作”成立时,才能据此讨论“通常不负责选择下一个元素”。
- 追问:访问者能否自己遍历? 可以,但会混合遍历与操作职责,除非结构本身要求访问者控制递归。
- 追问:迭代器知道元素具体类型吗? 通常只暴露统一 Element 类型,具体操作仍可交给多态或访问者。
- 追问:Composite 中谁遍历子节点? 可以由组合节点、独立迭代器或访问者控制,需统一约定。
- 追问:只需打印集合该用谁? 普通循环通常足够,不必为了模式而模式。
- 追问:二者共享的目标是什么? 都降低客户端对对象结构细节的直接依赖,但解决的细节不同。
八、加强记忆
记忆时抓住这条主线:迭代器管遍历;访问者管操作扩展;两者可以配合使用;是否需要 accept 是区分访问者的重要信号。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。