文章目錄
- 🧳 訪問者模式(Visitor Pattern)深度解析
- 一、模式本質與核心價值
- 二、經典UML結構
- 三、Unity實戰代碼(游戲物品系統)
- 1. 定義元素與訪問者接口
- 2. 實現具體元素類
- 3. 實現具體訪問者
- 4. 對象結構管理
- 5. 客戶端使用
- 四、模式進階技巧
- 1. 動態訪問者注冊
- 2. 訪問者組合模式
- 3. 異步訪問處理
- 五、游戲開發典型應用場景
- 六、性能優化策略
- 七、模式對比與選擇
- 八、最佳實踐原則
- 九、常見問題解決方案
🧳 訪問者模式(Visitor Pattern)深度解析
——以Unity實現靈活數據操作與跨系統交互為核心案例
一、模式本質與核心價值
核心目標:
? 分離數據結構與數據操作,支持在不修改元素類的前提下定義新操作
? 集中相關操作,避免污染元素類代碼
? 實現雙重分派,動態選擇元素處理方法
關鍵術語:
- Visitor(訪問者接口):聲明訪問各類元素的接口
- ConcreteVisitor(具體訪問者):實現特定操作的訪問邏輯
- Element(元素接口):定義接受訪問者的方法
- ObjectStructure(對象結構):維護元素集合,提供遍歷接口
數學表達:
設元素集合E = {e?, e?, …, e?},訪問者V,則操作執行過程為:
?e ∈ E, e.Accept(V) → V.Visit(e)
二、經典UML結構
三、Unity實戰代碼(游戲物品系統)
1. 定義元素與訪問者接口
public interface IItem {void Accept(IItemVisitor visitor);
}public interface IItemVisitor {void Visit(Weapon weapon);void Visit(Potion potion);void Visit(QuestItem questItem);
}
2. 實現具體元素類
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. 實現具體訪問者
// 傷害計算訪問者
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. 對象結構管理
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. 客戶端使用
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. 動態訪問者注冊
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. 訪問者組合模式
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. 異步訪問處理
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) {// 異步處理武器}
}
五、游戲開發典型應用場景
-
成就系統觸發
public class AchievementVisitor : IItemVisitor {public void Visit(Weapon w) {if(w.Damage > 100) Unlock("POWER_WEAPON");} }
-
戰斗傷害計算
public class BattleDamageVisitor : IItemVisitor {private float _totalDamage;public void Visit(Weapon w) {_totalDamage += CalculateElementDamage(w);} }
-
場景序列化存檔
public class SceneSaveVisitor : IItemVisitor {private List<SerializableData> _sceneData = new();public void Visit(Enemy e) {_sceneData.Add(new EnemyData(e.Position, e.Health));} }
-
UI數據綁定
public class UIDataVisitor : IItemVisitor {public void Visit(Weapon w) {InventoryUI.UpdateWeaponSlot(w);} }
六、性能優化策略
策略 | 實現方式 | 適用場景 |
---|---|---|
訪問緩存 | 緩存頻繁訪問結果 | 復雜計算場景 |
批處理 | 合并多個訪問操作 | 大量元素遍歷 |
并行處理 | 使用Job System并行訪問 | CPU密集型操作 |
惰性求值 | 延遲執行非關鍵訪問 | 性能敏感場景 |
七、模式對比與選擇
維度 | 訪問者模式 | 策略模式 |
---|---|---|
關注點 | 跨類操作 | 算法替換 |
擴展方向 | 新增操作 | 新增算法 |
元素穩定性 | 元素類需穩定 | 策略可任意擴展 |
典型應用 | 數據序列化 | 戰斗計算 |
八、最佳實踐原則
- 元素接口穩定:避免頻繁修改元素類接口
- 訪問者單一職責:每個訪問者專注一個功能領域
- 防御性訪問:處理未知元素類型
public class SafeVisitor : IItemVisitor {public void Visit(IItem item) {if(item is Weapon w) VisitWeapon(w);else Debug.LogWarning($"未知物品類型:{item.GetType()}");} }
- 訪問順序控制:
public void ProcessItems(IItemVisitor visitor) {// 按優先級排序處理foreach(var item in _items.OrderBy(i => i.Priority)) {item.Accept(visitor);} }
九、常見問題解決方案
Q1:如何處理新增元素類型?
→ 使用反射擴展訪問者
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:如何避免循環依賴?
→ 引入中間接口層
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:如何調試復雜訪問流程?
→ 實現訪問日志代理
public class LoggingVisitorProxy : IItemVisitor {private IItemVisitor _wrapped;public void Visit(Weapon w) {Debug.Log($"開始處理武器:{w.Name}");_wrapped.Visit(w);Debug.Log("武器處理完成");}
}
上一篇 【行為型之模板方法模式】游戲開發實戰——Unity標準化流程與可擴展架構的核心實現