设计模式之观察者模式
在日常生活中,交通信号灯指挥者日益拥挤的城市交通。红灯亮,汽车停止;绿灯亮,汽车继续前行;在这个过程中,交通信号灯是汽车的观察目标,而汽车则是观察者。随着交通信号灯的变化,汽车的行为也会随之变化,一盏交通信号灯可以指挥多辆汽车。在软件系统中,有些对象之间也存在类似交通信号灯和汽车之间的关系,一个对象的状态或行为的变化将会导致其他对象的状态或者行为也发生改变,它们之间将产生联动,正所谓牵一发而动全身。为了更好地描述对象之间存在的这种一对多的联动,观察者模式应运而生。
一、多人联机对战游戏的设计
需求背景:M公司欲开发一款多人联机对战游戏,在游戏中,多个游戏玩家可以加入同一战队组成联盟,当战队中某一成员收到敌人攻击时将给所有其他盟友发送通知,盟友收到通知后将作出响应。M公司开发人员需要提供一个设计方案来实现战队成员之间的联动。
M公司开发人员通过分析,发现在该系统中战队成员之间的联动过程可以简单描述如下:
联盟成员收到攻击 => 发送通知给盟友 => 盟友作出响应
如果每个联盟成员都需要持有盟友的信息才能及时通知每一位盟友,因此这样系统开销较大。因此,M公司开发人员决定引入一个新角色“战队控制中心”来负责维护和管理每个战队所有成员的信息,如下图所示:
二 观察者模式
观察者模式是一种使用频率最高的设计模式之一,用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应作出反应。
2.1 观察者模式类图
注意:由于Player需要向控制中心发送消息,控制中心又需要给所有玩家发送消息,在实现过程中存在头文件相互引用的问题,特引入IControlCenter类。
2.2 代码实现
(1)抽象观察者类
class IObserver { public: IObserver(char *pName = NULL){} ~IObserver(){} virtual void Help() = 0; virtual void BeAttacked(IControlCenter *pControlCenter) = 0; };
(2)实体CPlayer类
class Player : public IObserver { public: Player(char *pName = NULL) { size_t nLen = strlen(pName); memcpy(m_pName, pName, nLen + 1); } ~Player(){} void Help() { cout << m_pName << ":" << "坚持住,马上支援!" << endl; } void BeAttacked(IControlCenter *pControlCenter) { cout << m_pName << ":" << "我被攻击,请求支援!" << endl; pControlCenter->NotifyObserver(m_pName); } private: char m_pName[NAME_LENGTH]; };
(3)抽象控制中心
#pragma once class IControlCenter { public: IControlCenter(){} ~IControlCenter(){} virtual void NotifyObserver(char* pName) = 0; };
(4)实体控制中心
#pragma once #include <map> using namespace std; #include "IControlCenter.h" #include "IObserver.h" class CControlCenter:public IControlCenter { public: CControlCenter(){} ~CControlCenter(){} void Regesiter(char* pName, IObserver* pObserver) { m_ObserverMap[pName] = pObserver; cout << "通知:" << pName <<" " << "加入战队!" << endl; } void UnRegister(char *pName) { map<char*, IObserver*>::iterator iter; for (iter = m_ObserverMap.begin(); iter != m_ObserverMap.end(); iter ++) { if (0 == strcmp(pName, iter->first)) { m_ObserverMap.erase(iter); cout << pName <<" " << "离开战队!" << endl; break; } } } void NotifyObserver(char* pName) { cout << "通知:" << "盟友们,"<< pName <<"正在被攻击" << endl; map<char*, IObserver*>::iterator iter; for (iter = m_ObserverMap.begin(); iter != m_ObserverMap.end(); iter ++) { if (0 != strcmp(pName, iter->first)) { IObserver *pPlayer = iter->second; pPlayer->Help(); } } } private: map<char*, IObserver*> m_ObserverMap; };
2.3 测试
(1)测试代码
#include "stdio.h" #include "ControlCenter.h" #include "IObserver.h" void main() { CControlCenter *pControlCenter = new CControlCenter(); IObserver *pPlayer1 = new Player("张三"); IObserver *pPlayer2 = new Player("李四"); IObserver *pPlayer3 = new Player("王麻子"); pControlCenter->Regesiter("张三", pPlayer1); pControlCenter->Regesiter("李四", pPlayer2); pControlCenter->Regesiter("王麻子",pPlayer3); pPlayer1->BeAttacked(pControlCenter); }
(2)结果
三、观察者模式与MVC
在当前流行的MVC(Model-View-Controller)结构中也应用了观察者模式,它包含了3个角色:模型、视图和控制器。其中,模型可对应观察者模式中的观察目标,而视图则对应于观察者,控制器充当二者之间的中介者。当模型层的数据发生改变时,视图将会自动改变其显示内容,如下图所示:
四、观察者模式总结
4.1 主要优点
(1)可以实现表示层和数据逻辑层的分离 => 各种不同的表示层可以充当具体观察者
(2)支持广播通信,观察目标会向已注册的观察者对象发送通知 => 简化一对多系统设计的难度
(3)增加新的观察者无须修改原有系统代码 => 满足开闭原则
4.2 主要缺点
(1)如果一个观察目标有很多直接和间接的观察者 => 所有观察者收到通知会花费大量时间
(2)如果观察者和观察目标之间存在循环依赖 => 可能导致系统崩溃
4.3 应用场景
(1)一个抽象模型有两个方面,其中一个方面依赖于另一个方面 => 封装起来使其独立改变和复用
(2)一个对象的改变将导致一个或多个其他对象也发生改变,但并不知道具体有多少个对象将要发生改变 => 最熟悉的陌生人