当前位置: 首页 > news >正文

C++设计模式_行为型模式_观察者模式Observer(发布-订阅(Publish-Subscribe))

本文记录行为型设计模式之观察者模式(发布订阅模式)。

一个遍历问题导致的低效率范例

背景:之前的举得例子都是单机类游戏,现在公司为了增加游戏收入,将单机类游戏改为网络游戏,主要增加了两个功能:
功能1:游戏中游戏玩家必须实现游戏中玩家群体之间的战争,因为战争会消耗各种道具和物资,这些都要充钱买。为此,增加了家族概念,一个家族可容纳20人,不同家族的玩家之间可以根据游戏规则在指定的时间和地点通过战斗获利。
功能2:家族成员聊天,会被同家族的其他人看到。

namespace sp1
{// 用一个全局list 存放都有玩家class Fighter;list<Fighter*> g_playerList;class Fighter{public:Fighter(int playerID, string playerName){m_platerID = playerID;m_playerName = playerName;m_playerFamilyID = -1;    // 初始值为-1,表示没有家族}virtual ~Fighter(){}public:// 加入某个家族,设置游戏玩家的家族IDvoid joinFamily(int familyID)   {m_playerFamilyID = familyID;}// 离开家族,设置游戏玩家的家族ID为-1void leaveFamily(){m_playerFamilyID = -1;}// 游戏玩家说一句话 void sayWords(string msg){if (m_playerFamilyID != -1){// 该玩家有家族,通知家族成员for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter){if (m_playerFamilyID == (*iter)->m_playerFamilyID){(*iter)->notifyFamily((*iter), msg);}}}}private:void notifyFamily(Fighter *otherPlayer,string msg){// 通知家族成员cout << "玩家 " << otherPlayer->m_playerName << "家族号:" << otherPlayer->m_playerFamilyID << " 说了" << msg <<   endl;}public: // 为了方便,这里把成员变量都设置为publicint m_platerID;string m_playerName;int m_playerFamilyID;};// 创建游戏玩家class Warriar : public Fighter{public:Warriar(int playerID, string playerName) : Fighter(playerID, playerName){}~Warriar(){}};;// 法师class Magic : public Fighter{public:Magic(int playerID, string playerName) : Fighter(playerID, playerName){}~Magic(){}};;
}int main()
{// 创建有家族的玩家 1 2 3 sp1::Fighter *warriar1 = new sp1::Warriar(1, "战士1");sp1::Fighter *warriar2 = new sp1::Warriar(2, "战士2");sp1::Fighter *magic1 = new sp1::Magic(3, "法师");warriar1->joinFamily(100);warriar2->joinFamily(100);magic1->joinFamily(100);sp1::g_playerList.push_back(warriar1);sp1::g_playerList.push_back(warriar2);sp1::g_playerList.push_back(magic1);sp1::Fighter *warriar3 = new sp1::Warriar(4, "战士3");warriar3->joinFamily(200);sp1::g_playerList.push_back(warriar3);// 100家族的玩家warriar2说话warriar2->sayWords("我是战士2,我要去打副本了");/*玩家 战士1家族号:100 说了我是战士2,我要去打副本了玩家 战士2家族号:100 说了我是战士2,我要去打副本了玩家 法师家族号:100 说了我是战士2,我要去打副本了*/std::cout << "Hello World!\n";
}

从上边代码可以看出,只要同家族的一个玩家说一句话之后,同家族的其他玩家就可以收到这句话。但是,这种方式效率并不好,因为同家族的某个玩家说一句话,就要遍历一遍所有的玩家列表,假如有10 0000个玩家,每说一句话就要遍历10万次,效率并不高,下面引入观察者模式改进这种效率低的方式。

引入观察者(Observer)模式

相比于在整个游戏玩家列表中遍历每个玩家,不如只遍历本家族玩家,提高了效率。
观察者设计模式定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知,比如下面示例中,同家族的玩家说话,家族的人都会知道。 pwarriar2->sayWords(“我是战士2,我要去打副本了”, pnotifier);

namespace sp2
{class Fighter;class Notifier  // 通知器父类{public:// 向家族中加入一个玩家virtual void addToList(Fighter* player) = 0;// 从家族列表中删除一个玩家virtual void removeFromList(Fighter* player) = 0;// 玩家说了话,通知家族成员virtual void notifyFamily(Fighter* talker, string msg) = 0;virtual ~Notifier(){}};class Fighter{public:Fighter(int playerID, string playerName){m_platerID = playerID;m_playerName = playerName;m_playerFamilyID = -1;    // 初始值为-1,表示没有家族}virtual ~Fighter(){}public:// 加入某个家族,设置游戏玩家的家族IDvirtual void joinFamily(int familyID){m_playerFamilyID = familyID;}// 离开家族,设置游戏玩家的家族ID为-1virtual void leaveFamily(){m_playerFamilyID = -1;}// 获取家族IDvirtual int getFamilyID(){return m_playerFamilyID;}// 游戏玩家说一句话 virtual  void sayWords(string msg, Notifier* notifier){notifier->notifyFamily(this, msg);}public:virtual  void notifyWords(Fighter* otherPlayer, string msg){// 通知家族成员cout << "我是玩家 " << m_platerID << "收到了玩家 " << otherPlayer->m_playerName << " 家族号:" << otherPlayer->m_playerFamilyID << " 说了" << msg << endl;}public: // 为了方便,这里把成员变量都设置为publicint m_platerID;string m_playerName;int m_playerFamilyID;};// 创建游戏玩家class Warriar : public Fighter{public:Warriar(int playerID, string playerName) : Fighter(playerID, playerName){}~Warriar(){}};;// 法师class Magic : public Fighter{public:Magic(int playerID, string playerName) : Fighter(playerID, playerName){}~Magic(){}};;class NotifierMaster : public Notifier  // 通知器父类{public:// 向家族中加入一个玩家void addToList(Fighter* player){int tempFamilyID = player->getFamilyID();if (tempFamilyID != -1){map<int, list<Fighter*>>::iterator iter = m_mapFamilyList.find(tempFamilyID);if (iter != m_mapFamilyList.end()){// 该家族已经存在,直接添加对应的家族list中iter->second.push_back(player);}else{// 该家族不存在,创建家族list<Fighter*> tempList;tempList.push_back(player);// m_mapFamilyList.insert(pair<int, list<Fighter*>>(tempFamilyID, tempList));m_mapFamilyList.insert(make_pair(tempFamilyID, tempList));}}}// 从家族列表中删除一个玩家void removeFromList(Fighter* player){int tempFamilyID = player->getFamilyID();if (tempFamilyID == -1){return;}// 先查找是否存在map<int, list<Fighter*>>::iterator  iter = m_mapFamilyList.find(tempFamilyID);if (iter != m_mapFamilyList.end()){m_mapFamilyList[tempFamilyID].remove(player);}}// 玩家说了话,通知家族成员void notifyFamily(Fighter* talker, string msg){int tempFamilyID = talker->getFamilyID();if (tempFamilyID == -1){return;}// 先查找是否存在map<int, list<Fighter*>>::iterator  iter = m_mapFamilyList.find(tempFamilyID);if (iter != m_mapFamilyList.end()){for (list<Fighter*>::iterator iter = m_mapFamilyList[tempFamilyID].begin(); iter != m_mapFamilyList[tempFamilyID].end(); ++iter){(*iter)->notifyWords(talker, msg);}}return;}private:map<int, list<Fighter*>> m_mapFamilyList;};
}
int main()
{// 相比于 在整个游戏玩家列表中遍历每个玩家,不如只通知本家族玩家。// 观察者设计模式 定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于//它的对象都会自动得到通知。sp2::Fighter* pwarriar1 = new sp2::Warriar(1, "战士1");pwarriar1->joinFamily(100);sp2::Fighter* pwarriar2 = new sp2::Warriar(2, "战士2");pwarriar2->joinFamily(100);sp2::Fighter* pwarriar3 = new sp2::Warriar(3, "战士2");pwarriar3->joinFamily(100);// 创建通知器 sp2::Notifier* pnotifier = new sp2::NotifierMaster();/// 将玩家加入到通知器中pnotifier->addToList(pwarriar1);pnotifier->addToList(pwarriar2);pnotifier->addToList(pwarriar3);// 玩家2说话    pwarriar2->sayWords("我是战士2,我要去打副本了", pnotifier);std::cout << "Hello World!\n";
}

在这里插入图片描述
观察者模式又叫 发布-订阅(Publish-Subscribe);
观察者模式四种角色:
a) Subject(主题):观察目标,这里指Notifier类。
b) ConcreteSubject(具体主题):这里指TalkNotifier类。
c) Observer(观察者):这里指Fighter类。
d) ConcreteObserver(具体观察者):这里指F_Warrior和F_Mage子类。
观察者模式的特点:
a)在观察者和观察目标之间建立了一个抽象的耦合
b)观察目标会向观察者列表中的所有观察者发送通知。
c)可以通过增加代码来增加新的观察者或者观察目标,符合开闭原则

应用联想

a) 救援家族成员镖车
b) 将新闻推荐给符合其胃口的读者
c) 通过改变自身绘制的图形来真实的反应公司的销售数据。
d) 炮楼只会对30米内的玩家(列表内玩家)进行攻击。

UML

在这里插入图片描述

http://www.dtcms.com/a/490281.html

相关文章:

  • 怎么解决ModuleNotFoundError: No module named ‘Cython‘
  • 电子商务网站规划流程手机主题制作网站
  • 微信小程序 Button 组件 open-type 完全指南:从用户信息获取到客服分享的实战应用
  • 阿里云白皮书-架构
  • Rokid 开发空间小程序 实战
  • RAG系统搭建指南:5种主流框架的易用性和效果对比
  • 网站后台怎么添加代码哪里有免费的域名注册建网站
  • Jetson AGX Orin+GMSL+AI视觉开发套件,支持自动驾驶,机器人,工业视觉等应用
  • 【JETSON+FPGA+GMSL+AI】自动驾驶与移动机器人的摄像头如何实现高精度时间同步?
  • Java 设计模式——建造者模式:从原理到实战的极简指南
  • 怎么找做企业网站的雁塔区住房和城乡建设局网站
  • 哈尔滨电商企业服务器托管方案
  • 机器学习基础入门(第五篇):半监督学习与强化学习
  • 建网站解决方案代运营网店公司
  • 网站建设网页制作软件wordpress 移动到回收站发生错误
  • 5G安全深入解析:EAP-AKA、EAP-AKA‘与5G-AKA详解
  • YOLO 系列演进:从 V1 到 V2 的目标检测革命
  • 云栖实录|MaxCompute全新升级:AI时代的原生数据仓库
  • 基于Multi-Agent开发的SmartCare系统自动化运维管家
  • 终结浏览器隐患:自动化安全审计与报表
  • 笔记本卡顿,拖慢的不止是CAD看图评审速度
  • 电脑被捆绑软件缠上?3 步根治:紧急断网 + 深度清理 + 长效防护
  • 网站专业优化商城购物网站开发背景
  • 哪家做外贸网站好沈阳高端网页
  • GPU服务器深度解析:H100/H200的“机头”与“模组”架构详解
  • javaWeb-html、css-常见标签样式
  • 开发组件漏洞
  • 每日学习内容简单汇总记录
  • vue3 封装图片上传预览组件支持docx、excel、pdf、图片、txt格式
  • (3)Bug篇