访问者模式如何体现开闭原则?它又违反了什么?
简化版
访问者模式对新增操作开放,对修改元素类关闭,因此在操作维度体现开闭原则。但它对新增元素类型不友好,新增元素会修改 Visitor 接口和所有访问者实现,所以在元素维度并不满足开闭原则。
详细版
访问者模式的开闭原则要分方向看:
- 新增操作:新增一个 ConcreteVisitor 即可,元素类不用改。
- 新增元素:Visitor 接口要加方法,所有 ConcreteVisitor 都要改。
因此它不是绝对符合开闭原则,而是把扩展性集中给了“操作变化”方向。
如果系统中元素稳定、操作频繁变化,访问者模式非常合适。
如果系统中操作稳定、元素频繁变化,普通多态或策略模式可能更合适。
这也是访问者模式面试的高频考点:它是一种有明确代价的设计取舍。
完整版教学
一、开闭原则不是绝对的
开闭原则说的是对扩展开放,对修改关闭。但真实系统有多个变化方向,不可能对所有方向都完美开放。
设计模式经常做的是选择一个最可能变化的方向,让它更容易扩展,同时接受另一个方向的代价。
访问者模式就是典型例子。
二、操作维度开放
假设已有元素 A、B、C。现在要新增操作 X、Y、Z。
不用访问者时,可能要分别修改 A、B、C,把 X、Y、Z 方法加进去。
使用访问者后,只要新增 XVisitor、YVisitor、ZVisitor。这对操作扩展很友好。
三、元素维度关闭失败
如果新增元素 D,就麻烦了。Visitor 接口要加 visitD(D d),已有 XVisitor、YVisitor、ZVisitor 都要补实现。
这说明访问者模式没有让元素类型扩展变容易,甚至让它更难。
四、常见误区与工程判断
面试回答里不要简单说“访问者模式符合开闭原则”。更严谨的说法是:它在新增操作时符合开闭原则,在新增元素时会破坏开闭原则。
工程判断中,先识别未来变化点。如果业务专家告诉你节点类型基本不会变,但报表和分析需求很多,访问者模式就很稳。如果节点类型天天加,就应该换方案。
五、开闭原则在这里不是绝对命题
很多人回答这题会说“访问者符合开闭原则”,这不够严谨。访问者模式对新增操作符合开闭原则:新增一个统计访问者、导出访问者、校验访问者,不需要改已有元素类。但它对新增元素类型并不友好:新增一个元素,Visitor 接口和所有 ConcreteVisitor 往往都要改。
所以访问者模式体现的是“按变化方向应用开闭原则”。设计时要先判断系统未来更可能新增操作,还是更可能新增元素。如果操作变化远多于元素变化,访问者能让主要变化点保持开放;如果元素类型变化也很频繁,就不要强行说它符合开闭原则。
面试里可以把这题答成一个取舍:访问者不是让系统所有方向都关闭修改,而是把修改集中到更少、更可控的位置。优秀设计不是没有修改,而是让高频变化发生在预期的位置。
六、用工程约束检验答案
假设结构有 4 种元素、已有 6 个操作:增加第 7 个操作通常只新增 1 个访问者;增加第 5 种元素却会迫使 6 个访问者都变化。因此“符合开闭原则”必须带上扩展维度,不能把它说成无条件结论。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| 操作维度 | 对扩展开放 | 新增 ConcreteVisitor |
| 元素维度 | 修改成本高 | 改 Visitor 与全部实现 |
| 调用与遍历 | 通常稳定 | 继续复用 accept 和 ObjectStructure |
把关键关系压缩成一条可复述的路径:
变化轴 A:操作 +1 -> 新增访问者
变化轴 B:元素 +1 -> 修改访问者族
选择模式前比较 A、B 的频率
让高频变化落在低成本一侧
开闭原则不是让所有方向都零修改,而是围绕可预见的高频变化建立稳定边界。
落地前可以再按下面 3 步复核:
- 先说明“操作维度”的核心机制:对扩展开放;再交代边界:新增 ConcreteVisitor。
- 接着分析“元素维度”:修改成本高;不能遗漏对应代价或结果:改 Visitor 与全部实现。
- 最后用“调用与遍历”检查方案:通常稳定;验收时确认继续复用 accept 和 ObjectStructure。
这三项构成完整判断链:先讲清操作维度,再说明元素维度,最后用调用与遍历检验实现是否越界。
面试中若能给出违反“继续复用 accept 和 ObjectStructure”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“操作维度”就认为方案成立。 必须同时说明核心机制“对扩展开放”和工程边界“新增 ConcreteVisitor”。
- 误区:把“元素维度”当成无条件结论。 只有在“修改成本高”成立时,才能据此讨论“改 Visitor 与全部实现”。
- 追问:访问者是否违反开闭原则? 对新增元素不友好,但对新增操作开放,答案取决于讨论的变化轴。
- 追问:能否用默认 visit 缓解新增元素? 默认方法能减少编译修改,却可能静默漏掉必要业务处理。
- 追问:用基类兜底好吗? 仅当新元素确实允许统一语义时才合适,否则会掩盖遗漏。
- 追问:元素频繁新增怎么办? 优先考虑多态、策略或把行为留在元素中,而非强行使用访问者。
- 追问:怎样量化是否值得? 比较元素类型变化次数、操作变化次数及每次联动修改范围。
八、加强记忆
记忆时抓住这条主线:访问者模式只在操作维度更符合开闭原则;新增访问者容易,新增元素困难;开闭原则要结合变化方向理解;设计模式本质是有取舍地管理变化。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。