文章目录
-
-
- [🧳 访问者模式(Visitor Pattern)深度解析](#🧳 访问者模式(Visitor Pattern)深度解析)
-
- 一、模式本质与核心价值
- 二、经典UML结构
- 三、Unity实战代码(游戏物品系统)
-
- [1. 定义元素与访问者接口](#1. 定义元素与访问者接口)
- [2. 实现具体元素类](#2. 实现具体元素类)
- [3. 实现具体访问者](#3. 实现具体访问者)
- [4. 对象结构管理](#4. 对象结构管理)
- [5. 客户端使用](#5. 客户端使用)
- 四、模式进阶技巧
-
- [1. 动态访问者注册](#1. 动态访问者注册)
- [2. 访问者组合模式](#2. 访问者组合模式)
- [3. 异步访问处理](#3. 异步访问处理)
- 五、游戏开发典型应用场景
- 六、性能优化策略
- 七、模式对比与选择
- 八、最佳实践原则
- 九、常见问题解决方案
-
🧳 访问者模式(Visitor Pattern)深度解析
------以Unity实现灵活数据操作 与跨系统交互为核心案例
一、模式本质与核心价值
核心目标 :
✅ 分离数据结构与数据操作 ,支持在不修改元素类的前提下定义新操作
✅ 集中相关操作 ,避免污染元素类代码
✅ 实现双重分派,动态选择元素处理方法
关键术语:
- Visitor(访问者接口):声明访问各类元素的接口
- ConcreteVisitor(具体访问者):实现特定操作的访问逻辑
- Element(元素接口):定义接受访问者的方法
- ObjectStructure(对象结构):维护元素集合,提供遍历接口
数学表达 :
设元素集合E = {e₁, e₂, ..., eₙ},访问者V,则操作执行过程为:
∀e ∈ E, e.Accept(V) → V.Visit(e)
二、经典UML结构
accept accept <<interface>> IVisitor +VisitWeapon(Weapon) +VisitPotion(Potion) DamageCalculator +VisitWeapon() +VisitPotion() <<interface>> IItem +Accept(IVisitor) Weapon +Accept() Potion +Accept()
三、Unity实战代码(游戏物品系统)
1. 定义元素与访问者接口
csharp
public interface IItem {
void Accept(IItemVisitor visitor);
}
public interface IItemVisitor {
void Visit(Weapon weapon);
void Visit(Potion potion);
void Visit(QuestItem questItem);
}
2. 实现具体元素类
csharp
public class Weapon : MonoBehaviour, IItem {
public int Damage;
public string ElementType;
public void Accept(IItemVisitor visitor) {
visitor.Visit(this);
}
}
public class Potion : MonoBehaviour, IItem {
public float HealAmount;
public int Charges;
public void Accept(IItemVisitor visitor) {
visitor.Visit(this);
}
}
3. 实现具体访问者
csharp
// 伤害计算访问者
public class DamageCalculator : IItemVisitor {
private float _totalDamage;
public void Visit(Weapon weapon) {
_totalDamage += weapon.Damage * (weapon.ElementType == "Fire" ? 1.2f : 1f);
}
public void Visit(Potion potion) {
// 药水不贡献伤害
}
public void Visit(QuestItem questItem) {
// 任务物品不贡献伤害
}
public float GetTotalDamage() => _totalDamage;
}
// 存档序列化访问者
public class SaveVisitor : IItemVisitor {
private List<byte[]> _serializedData = new();
public void Visit(Weapon weapon) {
var data = Encoding.UTF8.GetBytes($"Weapon|{weapon.Damage}|{weapon.ElementType}");
_serializedData.Add(data);
}
public void Visit(Potion potion) {
var data = Encoding.UTF8.GetBytes($"Potion|{potion.HealAmount}|{potion.Charges}");
_serializedData.Add(data);
}
public byte[] GetSaveData() {
return _serializedData.SelectMany(arr => arr).ToArray();
}
}
4. 对象结构管理
csharp
public class InventorySystem : MonoBehaviour {
private List<IItem> _items = new();
public void AddItem(IItem item) => _items.Add(item);
public void ProcessItems(IItemVisitor visitor) {
foreach(var item in _items) {
item.Accept(visitor);
}
}
}
5. 客户端使用
csharp
public class GameManager : MonoBehaviour {
[SerializeField] private InventorySystem _inventory;
void Start() {
// 计算总伤害
var damageCalc = new DamageCalculator();
_inventory.ProcessItems(damageCalc);
Debug.Log($"总伤害值:{damageCalc.GetTotalDamage()}");
// 生成存档数据
var saver = new SaveVisitor();
_inventory.ProcessItems(saver);
SaveToFile(saver.GetSaveData());
}
}
四、模式进阶技巧
1. 动态访问者注册
csharp
public class DynamicVisitor : IItemVisitor {
private Dictionary<Type, Action<object>> _handlers = new();
public void RegisterHandler<T>(Action<T> handler) where T : IItem {
_handlers[typeof(T)] = obj => handler((T)obj);
}
public void Visit(Weapon weapon) => InvokeHandler(weapon);
public void Visit(Potion potion) => InvokeHandler(potion);
private void InvokeHandler<T>(T item) where T : IItem {
if(_handlers.TryGetValue(typeof(T), out var handler)) {
handler(item);
}
}
}
2. 访问者组合模式
csharp
public class CompositeVisitor : IItemVisitor {
private List<IItemVisitor> _visitors = new();
public void AddVisitor(IItemVisitor visitor) => _visitors.Add(visitor);
public void Visit(Weapon weapon) {
foreach(var v in _visitors) v.Visit(weapon);
}
public void Visit(Potion potion) {
foreach(var v in _visitors) v.Visit(potion);
}
}
3. 异步访问处理
csharp
public class AsyncVisitor : MonoBehaviour, IItemVisitor {
public async Task ProcessAsync(InventorySystem inventory) {
var tasks = new List<Task>();
foreach(var item in inventory.Items) {
tasks.Add(Task.Run(() => item.Accept(this)));
}
await Task.WhenAll(tasks);
}
public void Visit(Weapon weapon) {
// 异步处理武器
}
}
五、游戏开发典型应用场景
-
成就系统触发
csharppublic class AchievementVisitor : IItemVisitor { public void Visit(Weapon w) { if(w.Damage > 100) Unlock("POWER_WEAPON"); } }
-
战斗伤害计算
csharppublic class BattleDamageVisitor : IItemVisitor { private float _totalDamage; public void Visit(Weapon w) { _totalDamage += CalculateElementDamage(w); } }
-
场景序列化存档
csharppublic class SceneSaveVisitor : IItemVisitor { private List<SerializableData> _sceneData = new(); public void Visit(Enemy e) { _sceneData.Add(new EnemyData(e.Position, e.Health)); } }
-
UI数据绑定
csharppublic class UIDataVisitor : IItemVisitor { public void Visit(Weapon w) { InventoryUI.UpdateWeaponSlot(w); } }
六、性能优化策略
策略 | 实现方式 | 适用场景 |
---|---|---|
访问缓存 | 缓存频繁访问结果 | 复杂计算场景 |
批处理 | 合并多个访问操作 | 大量元素遍历 |
并行处理 | 使用Job System并行访问 | CPU密集型操作 |
惰性求值 | 延迟执行非关键访问 | 性能敏感场景 |
七、模式对比与选择
维度 | 访问者模式 | 策略模式 |
---|---|---|
关注点 | 跨类操作 | 算法替换 |
扩展方向 | 新增操作 | 新增算法 |
元素稳定性 | 元素类需稳定 | 策略可任意扩展 |
典型应用 | 数据序列化 | 战斗计算 |
八、最佳实践原则
-
元素接口稳定:避免频繁修改元素类接口
-
访问者单一职责:每个访问者专注一个功能领域
-
防御性访问 :处理未知元素类型
csharppublic class SafeVisitor : IItemVisitor { public void Visit(IItem item) { if(item is Weapon w) VisitWeapon(w); else Debug.LogWarning($"未知物品类型:{item.GetType()}"); } }
-
访问顺序控制 :
csharppublic void ProcessItems(IItemVisitor visitor) { // 按优先级排序处理 foreach(var item in _items.OrderBy(i => i.Priority)) { item.Accept(visitor); } }
九、常见问题解决方案
Q1:如何处理新增元素类型?
→ 使用反射扩展访问者
csharp
public class ReflectionVisitor {
private Dictionary<Type, MethodInfo> _methods = new();
public void Visit(IItem item) {
var type = item.GetType();
if(_methods.TryGetValue(type, out var method)) {
method.Invoke(this, new[]{item});
}
}
}
Q2:如何避免循环依赖?
→ 引入中间接口层
csharp
public interface IWeaponVisitor {
void VisitWeapon(Weapon weapon);
}
public class DamageCalculator : IItemVisitor, IWeaponVisitor {
public void Visit(Weapon w) => VisitWeapon(w);
public void VisitWeapon(Weapon w) { /* 具体逻辑 */ }
}
Q3:如何调试复杂访问流程?
→ 实现访问日志代理
csharp
public class LoggingVisitorProxy : IItemVisitor {
private IItemVisitor _wrapped;
public void Visit(Weapon w) {
Debug.Log($"开始处理武器:{w.Name}");
_wrapped.Visit(w);
Debug.Log("武器处理完成");
}
}