访问者模式是一种行为型设计模式, 核心思想是: 分离对象的数据结构(”被访问者”)和对数据的操作(”访问者”) 让操作可以独立扩展, 而不五修改被访问者代码
 
简单来说: 被访问者(比如游戏中的角色\道具)只负责”存在”, 访问者(比如道具效果\伤害计算)负责”对它做什么”. 当需要新增操作时, 只需要加新的访问者, 不用改变被访问者的代码.
 
生活中的例子: 快递分拣
快递站里有各种包裹(被访问者: 文件\电器\生鲜), 分拣员(访问者: 普通分拣员\冷链分拣员) 负责对不同包裹做不同处理:
 
  • 普通分拣员: 文件 → 常温区, 电器 → 易碎区, 生鲜 → 暂存区;
  • 冷链分拣员: 文件 → 常温去, 电器 → 易碎区, 生鲜 → 冷链区;
 
包裹(被访问者)的结构不变 (都是”要被分拣的东西”), 但分拣规则(访问者的操作)可独立扩展(新增”加急分拣员”只需加新访问者, 不用改包裹)
 

Unity中的核心场景: 道具效果系统

假设我们开发一款RPG游戏, 有3种角色(玩家\友方NPC\地方怪物)和3种道具(生命药水\攻击Buff\陷阱). 每种道具对不同角色的效果完全不同
道具\角色
玩家(Player)
友方NPC(Ally)
敌方怪物(Enemy)
生命药水
恢复 100 血
恢复 80 血
扣 50 血(毒药水)
攻击Buff
攻击 + 20(持续5秒)
攻击 + 15(持续3秒)
无效(免疫buff)
陷阱
扣除 30 血 + 短暂硬直
扣除 20 血
扣除 60 血 + 2秒硬直

不用访问者模式的问题:

如果直接在角色脚本里写 if-else 判断道具类型:
  • 耦合度极高: 角色和道具效果强绑定;
  • 扩展困难: 新增道具(如防御Buff), 需要修改所有角色的代码;
  • 维护复杂: 道具效果逻辑分散在各个角色种, 不好统一管理.
 

用访问者模式重构: 核心角色

访问者模式有4个核心角色, 对应Unity场景如下:
模式角色
Unity场景映射
作用
抽象访问者(IVisitor)
IItemVisitor(道具访问者)
定义访问者所有”被访问者”(角色)的方法
具体访问者(ConcreteVisitor)
HealthPotionVisitor\AttackBuffVisitor
实现道具对不同角色的具体效果
抽象元素(IElement)
ICharacter(角色接口)
定义”接受访问者”的方法(Accept)
具体元素(ConcreteElement)
Player\AllyNPC\Enemy
实现Accept方法, 让访问者”访问”自己
对象结构(ObjectStructure)
CharacterManager(角色管理器)
管理所有角色, 提供遍历接口(可选)
  1. 定义抽象元素(ICharacter)
所有角色(被访问者)都要实现这个接口, 核心是 Accept 方法 ——— 让访问者能”访问”自己, 并调用访问者对应的处理逻辑.
 
  1. 定义抽象访问者(IItemVisitor)
所有道具(访问者)都要实现这个接口, 定义对每种角色的处理方法(VisitPlayer\VisitAlly\VisitEnemy).
 
  1. 实现具体元素(角色: Player\AllyNPC\Enemy)
每个角色实现 Accept 方法, 核心逻辑是: 调用访问者对应的”访问自己”的方法, 并把自己传进去.
角色本身只负责”持有自身数据”(血量\攻击力), 不关心道具效果(交给访问者处理).
 
  1. 实现具体访问者(道具: HealthPotion\AttackBuff\Trap)
每个道具(访问者)实现IItemVisitor, 定义对每种角色的具体效果 ——— 所有道具效果逻辑都集中在这里, 和角色解耦.
 
  1. 实现对象结构(CharacterManager)
管理所有角色, 提供”遍历所有角色并应用道具效果”的接口(可选, 但Unity种常用管理器简化调用)
 

Unity中实际使用

  1. 创建角色对象
      • 创建 3 个游戏对象(Player、AllyNPC、Enemy);
      • 分别挂载 PlayerAllyNPCEnemy 脚本;
      • 在 Start 方法中调用 CharacterManager.Instance.AddCharacter(this),将角色加入管理器。
  1. 创建道具按钮
      • 在 UI 中创建 3 个按钮(生命药水、攻击 Buff、陷阱);
      • 给每个按钮绑定 OnClick 事件,调用 CharacterManager.Instance.ApplyItemEffect(新的访问者实例)
        • 生命药水按钮:CharacterManager.Instance.ApplyItemEffect(new HealthPotionVisitor())
        • 攻击 Buff 按钮:CharacterManager.Instance.ApplyItemEffect(new AttackBuffVisitor())
        • 陷阱按钮:CharacterManager.Instance.ApplyItemEffect(new TrapVisitor())
  1. 运行测试
      • 点击不同按钮,控制台会输出每种道具对不同角色的效果;
      • 新增道具(如防御 Buff)时,只需新增 DefenseBuffVisitor 类(实现 IItemVisitor),无需修改任何角色脚本!
 

访问者模式的核心优点

  1. 解耦数据与操作: 角色(数据结构)和道具效果(操作)完全分离, 角色不用关心”道具对我做什么”, 道具不关心”角色用什么数据”(通过角色的公开方法操作).
  1. 扩展灵活: 新增道具只需加新的 IItemVisitor 实现, 不同改所有角色代码(符合开放/封比原则).
  1. 逻辑集中: 所有道具效果集中在访问者类中, 便于维护和修改(比如要调整生命药水的恢复量,只需要改 HealthPotionVisitor).
  1. 支持多态操作: 同一道具对不同角色有不同效果, 通过访问者的多态方法自动匹配.
 

访问者模式的适用场景(Unity中)

  1. 道具/技能效果系统: 如本例, 同一技能/道具对不同角色(玩家\敌人\NPC)有不同效果
  1. 伤害计算系统: 不同伤害类型(物理\魔法\真实伤害)对不同防御类型(护甲\魔抗\无防御)的计算逻辑.
  1. UI数据显示: 同一组数据(角色属性)需要在不同UI面板(角色面板\战斗面板\排行榜)中展示不同格式, 用访问者处理展示逻辑.
  1. 资源检查工具: 在编译器扩展中, 遍历场景中的所有 GameObject(被访问者), 用访问者检查是否有缺失的组件\无效的引用.
  1. 敌人弱点打击: 不同武器(访问者)对敌人不同部位(被访问者: 头部\身体\四肢)早场不同伤害(如弓箭打头部暴击, 剑打身体普通伤害).
 

访问者模式的注意事项

  1. 元素(被访问者)稳定时更适用: 如果需要频繁更新角色类型(如新增”中立单位”), 则选哟修改 IItemVisitor 接口(加 VisitNeutreal 方法)混合所有访问者实现, 成本较高.
  1. 依赖被访问者的公开接口: 访问者只能通过被访问者的公开方法操作数据, 不能直接访问私有字段(保证数据封装).
  1. 避免过渡使用: 如操作很少变化(比如道具效果固定), 则无需使用访问者模式, 直接在角色中处理更简单.
 

总结

访问者模式的核心是”谁访问谁, 做什么” ——— 被访问者(角色)提供”被访问”的入口(Accept), 访问者(道具)提供”访问后的操作”(VisitXXX). 在Unity中, 它特别适合”数据结构稳定\操作多变”的场景(如道具\技能\伤害系统), 能极大提升代码的可扩展性和可维护性.
 
比如后续要做”元素伤害系统”(火焰\冰霜\雷电伤害), 只需新增 FireDamageVisitor\IceDamageVisitor, 让敌人\玩家接受这些访问者的处理,完全不用动原有角色代码!
 

问题

  1. 用Unity形式讲解访问者模式时, 如何设计抽象元素和具体元素?
  1. 用Unity形式讲解访问模式时, 如何设计对象结构?
  1. 用Unity形式讲解访问模式时, 如何在场景中应用访问模式?