“在对象间定义一种一对多的依赖关系,以便当某对象的状态改变时,与它存在依赖关系的所有对象都能收到通知并自动进行更新”(摘自《游戏编程模式》)
- MVC的底层就是观察者模式
- 观察者模式理解图
- 两者是m : n的关系。被观察者可以被多个观察监视,一个观察者可以观察多个被观察者
- 观察者模式的意义是:把游戏逻辑从分散在各个主干代码上聚起来。
- 实现方法主要思想是:利用关键字作为事件标志建立一个消息通知系统,当特定的事件发生时,调用被观察者自身通知观察者的方法,(通知观察者),然后在观察者执行游戏逻辑判断。
- 发送消息的手段即调用方法
- 只要理解为如下:
成就系统
- 很多游戏都存在成就系统。成就系统关系到很多行为 —— 因为它本应存在于游戏中的各个角落,涉及到多方面的判断。成就系统中可能包含以下成就:
- 杀死100个野怪 (累计在角色身上的杀敌数)
- 从桥上坠落 (物理系统的碰撞检测)
- 飞行最高记录:1000米 (依旧是物理)
- 我们不可以将成就判定代码写在上述中的角色类、物理引擎上。 这就到了观察者模式大显身手的时候了。
其实这是可行的,但这种设计方法是糟糕的,会照成代码的不可维护
- 观察者:
class Observer { public: virtual ~Observer(); virtual void noNotify(const Entity& entity,Event event)=0; } //成就系统 class Achievements : public Observer { public: virtual void noNotify(const Entity& entity,Event event) { switch(event) { case EVENT_ENTITY_FELL: if(entity.isHero()&&heroIsOnBridge_) unLock(ACHIEVEMENT_FELL_OFF_BRIDGE); break; } } private: void unLock(Achievement ach) { //Unlock } bool heroIsOnBridge_; }
- 被观察者:(以从桥上坠落为例)
class Subject { private: Observer* observers[MAX_OBSERVER]; int numObservers; public: void addObserver() {} void removeObserver() {} void notityAll() {} //遍历并发送 } //从桥上坠落涉及到物理系统 class Physics : public Subject { public: void updateEntity(Entity& entity); }
存在问题
- 太快的后果
- 消息系统会存在消息堵塞问题。观察者模式是同步的,被观察者只有从观察者获得上一次消息的反馈才会继续工作,这是从其他地方会有新的消息传入,引发了堵塞。
- 远离UI线程:UI消息是同步的,在处理UI消息时,必须要马上完成响应并尽可能快的返回UI代码,这样才不会照成卡顿。
- 太多的动态内存分配
- 在上面的代码中,为了简便使用了固定长度的数组,但在实际开发中,更多人需要的是可变长度的数组,这时设计到动态分配内存
- 解决方案是可以将数组改为链表
链式Observer代替数组Observer
- 实现如下所示:(不给出解释了,链表的实现可以参考数据结构知识点)
class Subject { public: Subject():head_(NULL){} void addObserver() {} void removeObserver() {} void notityAll() {} //遍历并发送 private: Observer* head_; } class Observer { friend class Subject; public: Observer() : next_(NULL) {} private: Observer* next_; } void Subject::addObserver(Observer* observer) { observer->next_=head_; head_=observer; } void Subject::removeObserver(Observer* observer) { if(head_==observer) { head_=observer->next_ observer->next_=NULL; } Observer* current=head_; //链表遍历 while(current!=NULL) { if(current->next==observer) { current->next_=observer->next_; observer->next_=NULL; return; } current=current->next; } } void Subject::notify(const Entity& entity,Event event) { Observer* observer=head_; while(observer != NULL) { observer->onNotify(entity,event); observer=observer->next_; } }
余下的问题
- 销毁观察者or被观察者:因为我们都在运用指针。指针比较关键的注意点是——误删。由于指针指向的是地址,当我们不小心delete时,其他引用该地址的指针便会**指向一个空地址!! **
总结
- 观察者模式适用于一些不相关模块之间的通信问题。不适合用于单个紧凑的模块内部之间的通信
- 观察者模式中Subject中的观察者列表具有灵活性,你可以选择合适的数据结构进行设计。
知识点全部源于《Game Programming Patterns》,中文名《游戏编程模式》