Unity中观察者模式双向通知:是否需实现反观察者?
Unity中双向事件驱动Observer模式的优化方案
方案1:反观察者模式(双向Subject-Observer)
- 核心逻辑:让Observer也实现Subject接口,原Subject同时作为Observer订阅Observer的事件。当Observer内部发生状态变化时,主动通知订阅它的Subject。
- Unity实现示例:
定义统一的交互接口,让双方互相实现:
比如玩家(原Subject)实现public interface IEventSubject { void RegisterObserver(IEventObserver observer); void UnregisterObserver(IEventObserver observer); void NotifyObservers(string eventType, object data); } public interface IEventObserver { void OnNotify(string eventType, object data); }IEventSubject和IEventObserver,UI(原Observer)同理。玩家通知UI更新血量,UI触发技能时通知玩家执行对应逻辑。 - 优缺点:解耦彻底,双方都是独立的事件节点,能无缝接入大系统;但需要维护双向注册/注销逻辑,Unity中必须注意对象销毁时的注销操作,防止空引用报错。
方案2:全局事件中心(Unity生态最优解)
- 核心逻辑:引入一个全局单例的事件调度器,Subject和Observer只与事件中心交互,不需要互相持有引用。
- Unity实现示例:
使用方式:Subject通过public class EventCenter : MonoBehaviour { private static EventCenter _instance; public static EventCenter Instance => _instance; private readonly Dictionary<string, List<System.Action<object>>> _eventMap = new(); private void Awake() { _instance = this; DontDestroyOnLoad(gameObject); } public void Subscribe(string eventName, System.Action<object> callback) { if (_eventMap.ContainsKey(eventName)) { _eventMap[eventName].Add(callback); } else { _eventMap[eventName] = new List<System.Action<object>> { callback }; } } public void Unsubscribe(string eventName, System.Action<object> callback) { if (_eventMap.TryGetValue(eventName, out var callbacks)) { callbacks.Remove(callback); } } public void Publish(string eventName, object data) { if (_eventMap.TryGetValue(eventName, out var callbacks)) { foreach (var callback in callbacks.ToList()) { callback?.Invoke(data); } } } }EventCenter.Instance.Publish("EnemyDamaged", damageValue)发布事件;Observer订阅EventCenter.Instance.Subscribe("SkillTriggered", OnSkillTriggered),自身触发事件时再调用Publish通知。 - 优缺点:完全解耦,Subject和Observer无需知晓对方存在,接入大系统时仅需统一事件命名规范;Unity中可通过
DontDestroyOnLoad保证全局存在,配合对象销毁时的Unsubscribe避免内存泄漏。
方案3:改进现有标记通知方案(轻量迭代)
- 核心逻辑:在现有基础上优化标记机制,避免简单布尔值的模糊性。
- 实现方式:
- 定义明确的事件枚举(如
ObserverEventType.SkillUsed, ObserverEventType.CloseUI),替换布尔标记; - 在Observer中维护待处理事件队列,Subject在特定时机(如每帧、逻辑帧)主动拉取事件并处理:
public class PlayerSubject : MonoBehaviour { [SerializeField] private UIObserver _uiObserver; private void FixedUpdate() { if (_uiObserver.HasPendingEvents()) { foreach (var evt in _uiObserver.TakePendingEvents()) { HandleObserverEvent(evt); } } } }
- 定义明确的事件枚举(如
- 优缺点:改动量极小,适合现有项目快速迭代;但Subject需要主动轮询,耦合度高于前两种方案。
选型建议
- 长期维护、需接入大型系统的项目:优先选择全局事件中心,完全解耦且贴合Unity开发习惯;
- 希望保留Observer模式语义的场景:可以采用反观察者模式,但要严格管控双向注册的生命周期;
- 轻量需求、不想大规模改动现有代码:优化现有标记方案即可。
内容的提问来源于stack exchange,提问作者IndividualGames
相关产品推荐
相关产品推荐

