Loading... > 观察者模式,它将观察者和被观察者代码解耦 观察者设计模式 定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,**所有依赖于它的对象都会自动得到通知** # 一、定义 > 观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern) 在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。 观察者模式会对应不同的代码实现方式:有同步阻塞的实现方式,也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。 被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。 不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。 ## 举个例子 — 一个遍历问题导致的低效率范例 > 下面这个例子函数命名上并没有用到上面的函数命名,后序会更新TODO ```cpp class Fighter; //类前向声明 list<Fighter*> g_playerList; //玩家父类(以往的战斗者类) class Fighter { public: Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数 { m_iFamilyID = -1; //-1表示没有加入任何家族 } virtual ~Fighter() {} //析构函数 public: void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID { m_iFamilyID = tmpID; } public: void SayWords(string tmpContent) //玩家说了某句话 { if (m_iFamilyID != -1) { //该玩家属于某个家族,应该把聊天内容信息传送给该家族的其他玩家 for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter) { if (m_iFamilyID == (*iter)->m_iFamilyID) { //同一个家族的其他玩家也应该收到聊天信息 NotifyWords((*iter), tmpContent); } } } } private: void NotifyWords(Fighter* otherPlayer, string tmpContent) //其他玩家收到了当前玩家的聊天信息 { //显示信息 cout << "玩家:" << otherPlayer->m_sPlayerName << "收到了玩家:" << m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl; } private: int m_iPlayerID; //玩家ID,全局唯一 string m_sPlayerName; //玩家名字 int m_iFamilyID; //家族ID }; //"战士"类玩家,父类为Fighter class F_Warrior :public Fighter { public: F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数 }; //"法师"类玩家,父类为Fighter class F_Mage :public Fighter { public: F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数 }; int main() { //创建游戏玩家 Fighter* pplayerobj1 = new F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。 pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100 g_playerList.push_back(pplayerobj1); //加入到全局玩家列表中 Fighter* pplayerobj2 = new F_Warrior(20, "李四"); pplayerobj2->SetFamilyID(100); g_playerList.push_back(pplayerobj2); Fighter* pplayerobj3 = new F_Mage(30, "王五"); pplayerobj3->SetFamilyID(100); g_playerList.push_back(pplayerobj3); Fighter* pplayerobj4 = new F_Mage(50, "赵六"); pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族 g_playerList.push_back(pplayerobj4); //当某个玩家聊天时,同族人都应该收到该信息 pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!"); //释放资源 delete pplayerobj1; delete pplayerobj2; delete pplayerobj3; delete pplayerobj4; return 0; } ``` 问题: 下面这段代码会遍历所有玩家,效率比较低,如果我们对玩家进行分类的话,利用观察者,我们直接通知某个组就行,只需遍历那个组,无需遍历全部元素。 ```cpp //该玩家属于某个家族,应该把聊天内容信息传送给该家族的其他玩家 for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter) { if (m_iFamilyID == (*iter)->m_iFamilyID) { //同一个家族的其他玩家也应该收到聊天信息 NotifyWords((*iter), tmpContent); } } ``` ## 3、利用观察者进行优化 ```cpp class Fighter; //类前向声明 class Notifier //通知器父类 { public: virtual void addToList(Fighter* player) = 0; //把要被通知的玩家加入到列表中 virtual void removeFromList(Fighter* player) = 0; //把不想被通知的玩家从列表中去除 virtual void notify(Fighter* talker, string tmpContent) = 0; //通知的一些细节信息 virtual ~Notifier() {} }; //玩家父类 class Fighter { public: Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数 { m_iFamilyID = -1; //-1表示没有加入任何家族 } virtual ~Fighter() {} //析构函数 public: void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID { m_iFamilyID = tmpID; } int GetFamilyID() //获取家族ID { return m_iFamilyID; } public: void SayWords(string tmpContent,Notifier *notifier) //玩家说了某句话 { notifier->notify(this, tmpContent); } //通知该玩家接收到其他玩家发送来的聊天信息,虚函数,子类可以覆盖以实现不同的功能 virtual void NotifyWords(Fighter* talker, string tmpContent) { //显示信息 cout << "玩家:" << m_sPlayerName << "收到了玩家:" << talker->m_sPlayerName << " 发送的聊天信息:" << tmpContent << endl; } private: int m_iPlayerID; //玩家ID,全局唯一 string m_sPlayerName; //玩家名字 int m_iFamilyID; //家族ID }; //"战士"类玩家,父类为Fighter class F_Warrior :public Fighter { public: F_Warrior(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数 }; //"法师"类玩家,父类为Fighter class F_Mage :public Fighter { public: F_Mage(int tmpID, string tmpName) :Fighter(tmpID, tmpName) {} //构造函数 }; //聊天信息通知器 class TalkNotifier :public Notifier { public: //将玩家增加到家族列表中来 virtual void addToList(Fighter* player) { int tmpfamilyid = player->GetFamilyID(); if (tmpfamilyid != -1) //加入了某个家族 { auto iter = m_familyList.find(tmpfamilyid); if (iter != m_familyList.end()) { //该家族id在map中已经存在 iter->second.push_back(player); //直接把该玩家加入到该家族 } else { //该家族id在map中不存在 list<Fighter*> tmpplayerlist; m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist)); //以该家族id为key,增加条目到map中 m_familyList[tmpfamilyid].push_back(player); //向该家族中增加第一个玩家 } } } //将玩家从家族列表中删除 virtual void removeFromList(Fighter* player) { int tmpfamilyid = player->GetFamilyID(); if (tmpfamilyid != -1) //加入了某个家族 { auto iter = m_familyList.find(tmpfamilyid); if (iter != m_familyList.end()) { m_familyList[tmpfamilyid].remove(player); } } } //家族中某玩家说了句话,调用该函数来通知家族中所有人 virtual void notify(Fighter* talker, string tmpContent) //talker是讲话的玩家 { int tmpfamilyid = talker->GetFamilyID(); if (tmpfamilyid != -1) //加入了某个家族 { auto itermap = m_familyList.find(tmpfamilyid); if (itermap != m_familyList.end()) { //遍历该玩家所属家族的所有成员 for (auto iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist) { (*iterlist)->NotifyWords(talker, tmpContent); } } } } private: //map中的key表示家族id,value代表该家族中所有玩家列表 map<int, list<Fighter*> > m_familyList;//同个家族放到同一个容器中来 }; int int main(int argc, const char** argv) { //创建游戏玩家 Fighter* pplayerobj1 = new F_Warrior(10, "张三"); //实际游戏中很多数据取自数据库。 pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100 Fighter* pplayerobj2 = new F_Warrior(20, "李四"); pplayerobj2->SetFamilyID(100); Fighter* pplayerobj3 = new F_Mage(30, "王五"); pplayerobj3->SetFamilyID(100); Fighter* pplayerobj4 = new F_Mage(50, "赵六"); pplayerobj4->SetFamilyID(200); //赵六和前面三人属于两个不同的家族 //创建通知器 Notifier* ptalknotify = new TalkNotifier(); //玩家增加到家族列表中来,这样才能收到家族聊天信息 ptalknotify->addToList(pplayerobj1); ptalknotify->addToList(pplayerobj2); ptalknotify->addToList(pplayerobj3); ptalknotify->addToList(pplayerobj4); //某游戏玩家聊天,同族人都应该收到该信息 pplayerobj1->SayWords("全族人立即到沼泽地集结,准备进攻!", ptalknotify); cout << "王五不想再收到家族其他成员的聊天信息了---" << endl; ptalknotify->removeFromList(pplayerobj3); //将王五从家族列表中删除 pplayerobj2->SayWords("请大家听从族长调遣,前往沼泽地!", ptalknotify); //释放资源 delete pplayerobj1; delete pplayerobj2; delete pplayerobj3; delete pplayerobj4; delete ptalknotify; return 0; } ``` 回到观察者的定义: 在这个例子中,多个玩家(观察者)依赖于这个通知器,但玩家其实并不知道通知器怎么变化的。只是知道这个通知器变化了。 这个例子中观察者模式的四种角色 A、Subject(主题):观察目标,这里指Notifier类。 B、ConcreteSubject(具体主题):这里指TalkNotifier类。 C、Observer(观察者):这里指Fighter类。 D、ConcreteObserver(具体观察者):这里指F\_Warrior和F\_Mage子类。 最后修改:2025 年 07 月 01 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏