设计模式 --- 观察者模式

发布于:2025-04-15 ⋅ 阅读:(45) ⋅ 点赞:(0)

观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。

优点:

​​1.解耦性强​​
​​观察者(订阅者)与主题(发布者)通过抽象接口通信,不直接依赖具体实现,​​修改主题或观察者的代码时,无需影响对方,提升代码可维护性。
2.​​动态订阅机制​​
​​观察者可随时注册或注销,运行时灵活调整事件响应,支持热插拔功能,增强系统扩展性。
3.​​一对多通信高效​​
一个主题状态变化可同时通知多个观察者,避免重复调用逻辑,提升事件处理效率。
​​4.支持事件驱动架构​​
通过事件总线(Event Bus)实现全局通信, ​​简化跨模块通信,适合复杂系统交互。

缺点:

1.​性能开销​​:
高频事件(如每帧更新)或大量观察者时,遍历通知列表耗时(物理引擎的CollisionEvent在百个对象碰撞时,触发千次通知)。
​​优化​​
1.使用事件合并(如积累多次位置更新后批量通知)。
2.异步处理(将事件推送到队列,分帧处理)。
2.​​内存泄漏风险​​:
​​观察者未正确注销时,Subject(主题者)持有其引用导致无法回收(Unity中,UI面板销毁前未取消订阅事件)。
​​优化​​
使用弱引用(WeakReference)或者使用扩展方法在MonoBehaviour绑定取消事件注册的方法,使其在OnDestroy时调用取消注册方法。

public static void UnregisterOnDestroy(this MonoBehaviour obj, Action unsubscribe) {
    obj.gameObject.AddComponent<OnDestroyDispatcher>().OnDestroyEvent += unsubscribe;
}

class OnDestroyDispatcher : MonoBehaviour {
    public event Action OnDestroyEvent;
    void OnDestroy() => OnDestroyEvent?.Invoke();
}

注:弱引用的使用 (会加一篇Unity中弱引用的测试使用的文章)。
3.​​事件顺序不可控​​:
​​观察者处理事件的顺序依赖注册顺序,可能导致逻辑错误。
​​优化​​:
引入优先级字段,按优先级排序观察者。
4.​调试困难​​:
​​问题​​:事件流分散,难以追踪事件触发源头和传递链路。
​​优化​​:
添加事件日志:记录每个事件的发布者和订阅者。

说明例子:

1.UML图:

2.实现:

1.实现Subject(主题者)基类:

    public abstract class Subject
    {
        List<Observer> m_Observers = new List<Observer>();

        //加入观察者
        public void Attach(Observer theObserver)
        {
            m_Observers.Add(theObserver);
        }

        //删除观察者
        public void Detach(Observer theObserver)
        {
            m_Observers.Remove(theObserver);
        }

        //通知所有观察者
        public void Notify()
        {
            foreach (Observer theObserver in m_Observers)
            {
                theObserver.Update();
            }
        }
    }

2.实现Observer(观察者)基类:

    public abstract class Observer
    {
        public abstract void Update();
    }

3.实现具体Subject:

    public class ConcreteSubject : Subject
    {
        string m_SubjectState;

        public void SetState(string state)
        {
            this.m_SubjectState = state;
            this.Notify();
        }

        public string GetState()
        {
            return this.m_SubjectState;
        }
    }

4.实现具体Observer:

   //实现Observer1
   public class ConcreteObserver1 : Observer
   {

       ConcreteSubject m_Subject = null;

       public ConcreteObserver1(ConcreteSubject theSubject)
       {
           this.m_Subject = theSubject;
       }

       public override void Update()
       {
           Debug.Log("ConcreteObserver1.Update");
           //获取Subject状态
           Debug.Log("ConcreteObserver1 : Subject 当前主题:" + m_Subject.GetState());
       }
   }

   //实现Observer2
   public class ConcreteObserver2 : Observer
   {

       ConcreteSubject m_Subject = null;

       public ConcreteObserver2(ConcreteSubject theSubject)
       {
           this.m_Subject = theSubject;
       }

       public override void Update()
       {
           Debug.Log("ConcreteObserver2.Update");
           //获取Subject状态
           Debug.Log("ConcreteObserver2 : Subject 当前主题:" + m_Subject.GetState());
       }
   }

游戏中使用场景:

​​1.成就/统计系统​​:监听关键游戏事件(击杀、死亡、收集)--->玩家击杀Boss后解锁成就。
​​2.UI更新​​:将UI元素与游戏数据解耦--->血量变化时自动刷新血条UI。
​​3.跨系统通信​​:避免系统间直接引用--->背包系统物品使用后通知技能系统。
​​4.AI行为触发​​:基于事件触发的敌人反应---->玩家进入警戒范围触发敌人警报。
​​5.网络同步​​:将本地事件广播给其他客户端--->玩家位置同步事件。

总结:

观察者模式的核心价值​​在于解耦和动态通信,但其性能、内存和调试问题需谨慎处理。
在游戏开发中,观察者模式适用于以下场景:
1.需要松耦合的跨系统通信。
2.需要动态处理大量事件类型
3.需要支持模块化扩展(如MOD系统)。

参考书籍:

《Hands-On Game Development Patterns with Unity 2019》

《设计模式与游戏完美开发》


网站公告

今日签到

点亮在社区的每一天
去签到