C#开发:如何避免带有不同依赖的switch-case语句?
解决方案:消除Switch语句,同时分离数据与行为
针对你的问题,有两种成熟的方案可以解决Switch膨胀问题,同时保持Event类(纯数据)与Engine类(业务逻辑)的解耦,符合开闭原则。
方案1:事件处理器注册表(推荐,灵活易扩展)
核心思路是给DungeonRunner维护一个事件类型-处理委托的映射字典,新增事件时只需注册对应处理器,无需修改核心遍历逻辑。
伪代码实现
public class DungeonRunner { private readonly Dictionary<Type, Action<IEvent>> _eventHandlers = new(); // 构造函数中注入引擎并注册处理器 public DungeonRunner(ICombatEngine combatEngine, ITreasureEngine treasureEngine, ITrapEngine trapEngine) { _eventHandlers.Add(typeof(CombatEvent), e => combatEngine.Run((CombatEvent)e)); _eventHandlers.Add(typeof(TreasureEvent), e => treasureEngine.Run((TreasureEvent)e)); _eventHandlers.Add(typeof(TrapEvent), e => trapEngine.Run((TrapEvent)e)); // 新增事件时,仅需在这里添加一行注册代码即可 } public void RunDungeon(List<IEvent> dungeonEvents) { foreach (var dungeonEvent in dungeonEvents) { if (_eventHandlers.TryGetValue(dungeonEvent.GetType(), out var handler)) { handler(dungeonEvent); } else { // 可选:处理未知事件,比如打日志警告 } } } }
优势
- 完全遵循开闭原则:新增事件类型无需修改
RunDungeon的循环逻辑 - 数据与行为彻底解耦:
Event类仍然是纯数据结构,不依赖任何Engine - 扩展性强:支持动态注册处理器(比如游戏运行时新增事件类型)
方案2:双分派模式(面向对象规范实现)
核心思路是通过IEvent的Accept方法,将自身分发到对应的处理器方法中,把类型判断逻辑从DungeonRunner转移到Event类,但仍保持Event不直接依赖Engine。
伪代码实现
首先定义核心接口:
public interface IEvent { void Accept(IEventProcessor processor); } public interface IEventProcessor { void ProcessCombatEvent(CombatEvent combatEvent); void ProcessTreasureEvent(TreasureEvent treasureEvent); void ProcessTrapEvent(TrapEvent trapEvent); // 新增事件时,在这里添加对应的处理方法 }
实现具体Event类(纯数据+分发逻辑):
public class CombatEvent : IEvent { // 纯数据字段,比如Monster、PlayerStats等 public void Accept(IEventProcessor processor) { processor.ProcessCombatEvent(this); } } public class TreasureEvent : IEvent { // 纯数据字段,比如Loot、Gold等 public void Accept(IEventProcessor processor) { processor.ProcessTreasureEvent(this); } }
实现处理器类,绑定引擎与事件逻辑:
public class DungeonEventProcessor : IEventProcessor { private readonly ICombatEngine _combatEngine; private readonly ITreasureEngine _treasureEngine; private readonly ITrapEngine _trapEngine; public DungeonEventProcessor(ICombatEngine combatEngine, ITreasureEngine treasureEngine, ITrapEngine trapEngine) { _combatEngine = combatEngine; _treasureEngine = treasureEngine; _trapEngine = trapEngine; } public void ProcessCombatEvent(CombatEvent combatEvent) { _combatEngine.Run(combatEvent); } public void ProcessTreasureEvent(TreasureEvent treasureEvent) { _treasureEngine.Run(treasureEvent); } public void ProcessTrapEvent(TrapEvent trapEvent) { _trapEngine.Run(trapEvent); } }
最后修改DungeonRunner:
public class DungeonRunner { private readonly IEventProcessor _eventProcessor; public DungeonRunner(IEventProcessor eventProcessor) { _eventProcessor = eventProcessor; } public void RunDungeon(List<IEvent> dungeonEvents) { foreach (var dungeonEvent in dungeonEvents) { dungeonEvent.Accept(_eventProcessor); } } }
优势
- 类型安全:无需
as或强制类型转换,编译时即可检查类型匹配 - 符合面向对象设计规范:将事件分发逻辑封装在对应
Event类中 - 同样遵循开闭原则:新增事件仅需扩展接口和实现类
方案选择建议
- 如果是快速迭代的小型项目,优先选注册表方案,代码更简洁,扩展成本更低
- 如果需要更严谨的架构设计,或者团队更倾向于面向对象规范,选双分派模式
内容的提问来源于stack exchange,提问作者ConnieMnemonic
相关产品推荐
相关产品推荐

