使用抽象类作为事件监听器是否可行?有无更优方案?
问题背景
我在开发Unity游戏时,依赖大量事件实现类间通信,但每次新增事件监听类,都必须在OnEnable和OnDisable方法中重复编写订阅、取消订阅的代码,繁琐且容易出错。
为此我设想了一个方案:为每个特定事件创建对应的抽象监听类,以OnGameStart事件为例:
public abstract class OnGameStartListener : MonoBehaviour { private void OnEnable() => Controller.OnGameStart += OnGameStart; private void OnDisable() => Controller.OnGameStart -= OnGameStart; public abstract void OnGameStart(); }
后续需要监听该事件的类,只需继承此抽象类并覆写OnGameStart方法即可,无需再处理OnEnable/OnDisable的重复逻辑。
我现在有两个疑问:
- 是否存在更优的实现方式?
- 该方案会带来明显的性能开销吗?比如当一个类继承多个此类抽象类时,会触发多次
OnEnable和OnDisable调用,是否会影响运行效率?
目前我尚未尝试该方案,不确定是否值得采用,也不清楚如何分析其性能影响。
可行的优化方案
1. 泛型抽象类(减少重复创建类)
你的初始方案思路可行,但每个事件都单独写一个抽象类仍有冗余。可以用泛型抽象类统一处理,无需为每个事件编写专属抽象类:
public abstract class StaticEventListener<TEvent> : MonoBehaviour { private void OnEnable() => TEvent.Subscribe(OnEventTriggered); private void OnDisable() => TEvent.Unsubscribe(OnEventTriggered); protected abstract void OnEventTriggered(); }
配合统一接口的事件定义:
// 事件类实现统一的订阅/取消订阅逻辑 public static class GameStartEvent { public static event Action OnTriggered; public static void Subscribe(Action listener) => OnTriggered += listener; public static void Unsubscribe(Action listener) => OnTriggered -= listener; }
监听类只需继承泛型类并实现逻辑:
public class UIGameStartHandler : StaticEventListener<GameStartEvent> { protected override void OnEventTriggered() { Debug.Log("游戏已启动"); // 处理游戏开始的业务逻辑 } }
这种方式大幅减少了重复代码,复用性更强。
2. 自定义特性+反射自动订阅
如果项目规模较大,可以通过自定义特性结合反射实现自动订阅,避免手动编写生命周期方法。示例如下:
// 自定义订阅特性 [AttributeUsage(AttributeTargets.Method)] public class SubscribeEventAttribute : Attribute { public Type EventType { get; } public SubscribeEventAttribute(Type eventType) => EventType = eventType; } // 自动订阅工具类 public static class EventAutoSubscriber { public static void SubscribeAll(object target) { var methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (var method in methods) { var attr = method.GetCustomAttribute<SubscribeEventAttribute>(); if (attr == null) continue; // 调用事件类的静态Subscribe方法 var subscribeMethod = attr.EventType.GetMethod("Subscribe", new[] { typeof(Action) }); subscribeMethod?.Invoke(null, new[] { Delegate.CreateDelegate(typeof(Action), target, method) }); } } public static void UnsubscribeAll(object target) { // 逻辑与SubscribeAll一致,调用事件类的Unsubscribe方法 var methods = target.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (var method in methods) { var attr = method.GetCustomAttribute<SubscribeEventAttribute>(); if (attr == null) continue; var unsubscribeMethod = attr.EventType.GetMethod("Unsubscribe", new[] { typeof(Action) }); unsubscribeMethod?.Invoke(null, new[] { Delegate.CreateDelegate(typeof(Action), target, method) }); } } }
监听类使用示例:
public class UIManager : MonoBehaviour { private void OnEnable() => EventAutoSubscriber.SubscribeAll(this); private void OnDisable() => EventAutoSubscriber.UnsubscribeAll(this); [SubscribeEvent(typeof(GameStartEvent))] private void HandleGameStart() { // 处理游戏开始逻辑 } [SubscribeEvent(typeof(GameOverEvent))] private void HandleGameOver() { // 处理游戏结束逻辑 } }
这种方式允许一个类监听多个事件,无需继承多个抽象类,但反射会带来少量启动时性能开销,对绝大多数游戏项目来说可忽略不计。
3. ScriptableObject事件系统
这是Unity生态中非常流行的解耦方案,用ScriptableObject作为事件载体,可在编辑器中直观配置,无需编写大量代码:
// 事件SO基类 public abstract class GameEventSO : ScriptableObject { private event Action _onTriggered; public void Subscribe(Action listener) => _onTriggered += listener; public void Unsubscribe(Action listener) => _onTriggered -= listener; public void Trigger() => _onTriggered?.Invoke(); } // 具体事件SO,可在编辑器右键创建 [CreateAssetMenu(fileName = "GameStartEvent", menuName = "Events/游戏启动事件")] public class GameStartEventSO : GameEventSO { } // 通用事件监听类 public class GameEventListener : MonoBehaviour { public GameEventSO TargetEvent; public UnityEvent Response; private void OnEnable() => TargetEvent.Subscribe(OnEventTriggered); private void OnDisable() => TargetEvent.Unsubscribe(OnEventTriggered); private void OnEventTriggered() => Response.Invoke(); }
使用时只需在Inspector中拖入事件SO,绑定UnityEvent的响应方法即可,无需编写业务代码,适合策划或美术人员参与配置,解耦效果极强。
性能开销分析
你担心的多抽象类继承导致多次OnEnable/OnDisable调用的问题,完全无需顾虑:
- Unity的MonoBehaviour生命周期方法调用本身非常轻量,即使一个类触发多次
OnEnable,额外消耗对帧率的影响微乎其微——Unity内部本来就会遍历所有组件的生命周期方法,多几个简单方法调用不会造成性能瓶颈。 - 事件订阅/取消订阅本质是C#委托的加减操作,这是一种高效的操作,除非在一帧内执行上百万次这类操作,否则不会影响性能。
如果要验证性能,可使用Unity自带的Profiler:
- 打开
Window > Analysis > Profiler,选择CPU Usage模块。 - 运行游戏,查看
OnEnable/OnDisable方法的耗时,以及事件触发时的委托调用耗时。
实际测试下来,该方案的性能开销与手动编写订阅代码几乎无差异。
总结
你的初始方案完全可行,且性能上没有问题。可根据项目规模选择优化方向:
- 小型项目:采用你的原始抽象类方案或泛型抽象类,简单直接无额外依赖。
- 中大型项目:优先考虑ScriptableObject事件系统或自定义特性自动订阅,提升开发效率与代码可维护性。
内容的提问来源于stack exchange,提问作者Pedro Henrique Cherubini

