观察者模式(observer pattern) / 发布-订阅模式(Publish-Subscribe)

news2024/9/25 23:20:35

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

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
#include <chrono>
#include <algorithm>
#include <set>
#include <queue>

using namespace std;

namespace _nmsp1
{
    class Fighter;
    list<Fighter*> g_playerList;

    //玩家父类(以往的战斗者类)
    class Fighter
    {
    public:
        Fighter(int tempID, string tmpName) :m_iPlayerID(tempID),m_sPlayerName(tmpName)
        {
            m_iFamilyID = -1; // -1表示没有加入任何家族
        }

        virtual ~Fighter() {}

        void SetFamilyID(int tmpID)
        {
            m_iFamilyID = tmpID;
        }

        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() {

    _nmsp1::Fighter* pplayerobj1 = new _nmsp1::F_Warrior(10,"战士张三"); //实际工程中数据一般来自数据库
    pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
    _nmsp1::g_playerList.push_back(pplayerobj1);

    _nmsp1::Fighter* pplayerobj2 = new _nmsp1::F_Warrior(20, "战士李四"); //实际工程中数据一般来自数据库
    pplayerobj2->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
    _nmsp1::g_playerList.push_back(pplayerobj2);

    _nmsp1::Fighter* pplayerobj3 = new _nmsp1::F_Mage(30, "法师王五"); //实际工程中数据一般来自数据库
    pplayerobj3->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100
    _nmsp1::g_playerList.push_back(pplayerobj3);

    _nmsp1::Fighter* pplayerobj4 = new _nmsp1::F_Mage(50, "法师赵六"); //实际工程中数据一般来自数据库
    pplayerobj4->SetFamilyID(200); //法师赵六和前面三个人属于不同的家族
    _nmsp1::g_playerList.push_back(pplayerobj4);

    //当某个玩家聊天时,同族人都应该收到消息
    pplayerobj1->sayWords("全族人立即到沼泽地集结,准备进攻!");



    delete pplayerobj1;
    delete pplayerobj2;
    delete pplayerobj3;
    delete pplayerobj4;


    return 0;
}

程序运行结果:
在这里插入图片描述可以看到程序运行结果正确的输出,同一家族的人都可以正确收到消息。
1.但是这个程序存在一定的问题就是:程序运行效率不不是很高,如果一个家族中有几万个玩家同是在线的话,那么通过这种遍历的方式,去一个一个发送消息的话,效率是非常低的。
2.当某个玩家不想收到消息的话,这种方式也做不到,即不能够指定某些玩家接收消息。
3.那么怎么样改进这种低效率的方式呢?采用观察者模式/发布-订阅模式了。

Observer Pattern / Publish-Subscribe Pattern

  1. 观察者设计模式定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。
  2. 观察者模式也被称为 发布-订阅模式(Publish-Subscribe)。
  3. 观察者模式的四种角色:a) Subject(主题):观察目标,这里指的是Notifier类。b)ConcreteSubject(具体主题):这里指的是TalkNotifier类。c) Observer(观察者):这里指 Fighter类。d) ConcreteObserver(具体观察者):这里指 F_Warrior类和 F_Mage类。
  4. 观察者模式的特点:a) 在观察者和观察目标之间建立了一个抽象耦合。b) 观察目标会向观察者列表中的所有观察者发送通知。c)可以通过增加代码来增加新的观察者或者观察目标,符合开闭原则。

下面代码的UML图,如下所示:
在这里插入图片描述

#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
#include <chrono>
#include <algorithm>
#include <set>
#include <queue>
#include <map>

using namespace std;

namespace _nmsp2
{
    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 tempID, string tmpName) :m_iPlayerID(tempID), m_sPlayerName(tmpName)
        {
            m_iFamilyID = -1; // -1表示没有加入任何家族
        }

        virtual ~Fighter() {}

        void SetFamilyID(int tmpID)
        {
            m_iFamilyID = tmpID;
        }

        int GetFamilyID()
        {
            return m_iFamilyID;
        }

        void sayWords(string tmpcontent,Notifier* notifier) //玩家说了某句话
        {
            notifier->notify(this, tmpcontent);
        }
    public:
        //通知该玩家接收到其他玩家发送来的聊天信息,虚函数,子类可以覆盖以实现不同功能的函数
        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())
                {
                    iter->second.push_back(player);
                }
                else
                {
                    list<Fighter*> tmpplaylist;
                    m_familyList.insert(make_pair(tmpfamilyid,tmpplaylist));
                    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())
                {
                    //iter->second.remove(player); //与下面的写法等价
                    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 main() {

    _nmsp2::Fighter* pplayerobj1 = new _nmsp2::F_Warrior(10, "战士张三"); //实际工程中数据一般来自数据库
    pplayerobj1->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100

    _nmsp2::Fighter* pplayerobj2 = new _nmsp2::F_Warrior(20, "战士李四"); //实际工程中数据一般来自数据库
    pplayerobj2->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100

    _nmsp2::Fighter* pplayerobj3 = new _nmsp2::F_Mage(30, "法师王五"); //实际工程中数据一般来自数据库
    pplayerobj3->SetFamilyID(100); //假设该玩家所在的家族的家族ID是100

    _nmsp2::Fighter* pplayerobj4 = new _nmsp2::F_Mage(50, "法师赵六"); //实际工程中数据一般来自数据库
    pplayerobj4->SetFamilyID(200); //法师赵六和前面三个人属于不同的家族

    _nmsp2::Notifier* ptalknotify = new _nmsp2::TalkNotifier();

    ptalknotify->addToList(pplayerobj1);
    ptalknotify->addToList(pplayerobj2);
    ptalknotify->addToList(pplayerobj3);
    ptalknotify->addToList(pplayerobj4);

    //某游戏玩家聊天,同族人都应该收到该信息
    pplayerobj1->sayWords("全族人立即到沼泽地集结,准备进攻!",ptalknotify);

    
    cout << endl;
    cout << "法师王五不想在收到家族其他成员的聊天信息了" << endl;
    cout << endl;

    ptalknotify->removeFromList(pplayerobj3);
    pplayerobj2->sayWords("请大家听从组长调遣,前往沼泽地",ptalknotify);
    


    delete pplayerobj1;
    delete pplayerobj2;
    delete pplayerobj3;
    delete pplayerobj4;
    delete ptalknotify;


    return 0;
}

程序运行结果为:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/127320.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个三臂非劣效性检验的包简介——“ThreeArmedTrials”

目录0引言1.模型分类2. R语言函数介绍2.1 函数总览2.2 GElesions函数&#xff08;数据集1&#xff09;2.3 opt_alloc_RET函数2.4 power_RET函数2.5 remission&#xff08;数据集2&#xff09;2.6 seizures函数&#xff08;数据集3&#xff09;2.7 T2lesions函数&#xff08;数据…

多线程基础部分

多线程基础部分1. 线程与进程的关系1.1 多线程启动1.2 线程标识1.2.1 Thread与Runnable1.3 线程状态2.线程池入门2.1 ThreadPoolExecutor2.2 创建线程池2.3 关闭线程池创建线程的几种方法参考1. 线程与进程的关系 1个进程包含1个或多个线程。 1.1 多线程启动 线程有两种启动…

骨传导耳机是怎么传声的、骨传导耳机的优点是什么

要说这两年最火的蓝牙耳机是哪款&#xff0c;大火的骨传导耳机绝对可以名列前茅&#xff0c;那可真是运动健身、需长时佩戴耳机党的神器&#xff01;如果你是搞运动的、健身的&#xff0c;或者是需要长时间佩戴耳机上网课的学生党&#xff0c;那一副靠谱的骨传导耳机绝对是必不…

LVGL学习笔记7 - GD32平台优化

目录 1. 颜色深度 2. 更新disp_init 3. 更新disp_flush 4. 改为IPA更新数据 5. 死机问题 学习过程中发现GD32平台的显示效果不佳&#xff0c;而且会出现死机的问题&#xff0c;需要优化一下平台代码。 1. 颜色深度 修改颜色深度为32bit。 #define LV_COLOR_DEPTH 32 2.…

时序引擎架构和实例演练

一、时序引擎介绍 开务数据库时序引擎是一款功能丰富、高性能的时序引擎&#xff0c;专为物联网、工业互联网、数字能源、金融等场景设计并优化。它能让大量设备、数据采集器每天产生的高达 TB 甚至 PB 级的数据得到高效实时的处理&#xff0c;对业务的运行状态进行实时的监测、…

银行卡数据标签的列举与使用

银行卡三要素&#xff1a;银行卡号、姓名、身份证号&#xff0c;银行卡四要素是指银行卡号、姓名、身份证号、手机号。对于从事信贷风控的小伙伴来讲&#xff0c;并不陌生。 银行卡信息的应用可能更熟悉的是客户信息核验&#xff0c;也就是针对信贷客户审批额度发放之前&#x…

SpringCloud系列(七)最详细最全面详述统一网关 Gateway

有道词典上对 Gateway 有大门口, 门道, 通道以及计算机术语中的网关之意, 其实对于网关这个概念是很好理解的, 例如有这样高档的小区车库, 当开车经过闸口的时候会识别你的车牌号, 识别成功后会自动将你的车库门打开; 其实计算机中的网关也是如此, 在 Spring Cloud 中网关的实现…

【1 - 决策树 - 原理部分】菜菜sklearn机器学习

课程地址&#xff1a;《菜菜的机器学习sklearn课堂》_哔哩哔哩_bilibili 第一期&#xff1a;sklearn入门 & 决策树在sklearn中的实现第二期&#xff1a;随机森林在sklearn中的实现第三期&#xff1a;sklearn中的数据预处理和特征工程第四期&#xff1a;sklearn中的降维算法…

LOAM和SSL-SLAM

今天来水两个激光SLAM的相关框架的学习笔记。 一、LOAM 首先介绍scan-to-scan map-to-map scan-to-map之间的关系&#xff1a; 1.scan-to-scan匹配 即两帧激光雷达数据之间的匹配&#xff0c;目的是求得从起始帧A到目标帧B的相对平移量与旋转矩阵。目前来说scan-toscan中&a…

Elasticsearch搜索引擎

The Elastic Stack, 包括 Elasticsearch【搜索&#xff0c;分析】、 Kibana【可视化】、 Beats 和 Logstash【数据的搜集】&#xff08;也称为 ELK Stack&#xff09;。能够安全可靠地获取任何来源、任何格式的数据&#xff0c;然后实时地对数据进行搜索、分析和可视化。 Elati…

安装压缩包版mysql

一、mysql-8.0.21-winx64.zip解压 二、在解压后的目录下添加data目录 三、配置环境变量 win7&#xff1a; ​ 我的电脑–>属性–>高级系统设置–>高级–>环境变量 ​ 在下面系统变量中 ​ 新建 ​ 变量名&#xff1a;MYSQL_HOME ​ 变量值&#xff1a;E:\MySQL\my…

常用的接口安全性保障手段

http接口有哪些安全问题 数据被抓包窃取数据被恶意篡改数据被爬取泄漏Token授权机制 用户使用用户名密码登录后服务器给客户端返回一个Token&#xff08;通常是UUID&#xff09;&#xff0c;并将Token-UserId以键值对的形式存放在缓存服务器中。服务端接收到请求后进行Token验…

UG NX二次开发(C#)-曲线-NXOpen.Curve初探

系列文章目录 `` 例如:第一章 初探NXOpen.Curve类 文章目录 系列文章目录1.前言2.NXOpen.Curve2. NXOpen.Curve包含的子类3.曲线类型的获取4.将曲线对象转换为子类类型1.前言 介绍下NXOpen.Curve类、Curve类型的获取、一些创建曲线的封装方法(包括直线、样条曲线、圆锥曲线…

OSM数据内容解析

OSM数据内容解析 数据简介 OpenStreetMap&#xff08;简称OSM&#xff0c;中文是公开地图&#xff09;&#xff0c;这是一个网上地图协作计划&#xff0c;目标是创造一个内容自由且能让所有人编辑的世界地图。是一款由网络大众共同打造的免费开源、可编辑的地图服务。 OSM采…

成功实施APS生产排程系统,必须具备哪些条件?

在许多生产管理者眼中&#xff0c;生产作业计划是不重要的&#xff0c;如果我们只停留在小加工作坊的规模&#xff0c;大脑就能把一个月的订单、物料、资源记得清清楚楚&#xff0c;那么生产计划排程的必要性确实不太大&#xff0c;但事实上&#xff0c;随着生产规模的扩大&…

JDK1.8中HashMap的resize()方法详解

JDK1.8中HashMap的resize()方法详解 文章目录JDK1.8中HashMap的resize()方法详解[toc]一、概述二、源码解析三、元素迁移四、小结在学习本文之前&#xff0c;默认大家已经有了HashMap源码的前置知识。 「集合底层」深入浅出HashMap底层源码 一、概述 resize()方法的代码比较长…

OpenHarmony#深入浅出学习eTs#(四)登陆界面UI

本项目Gitee仓地址&#xff1a;深入浅出eTs学习: 带大家深入浅出学习eTs (gitee.com) 一、明确目标 经过前面两章的学习&#xff0c;大家对Super Visual应该有了一个较为简单的认识&#xff0c;这一章就把前面的知识点串一下&#xff0c;使用Ark UI(Super Visual)赖模仿一个Q…

浅谈权限系统在多利熊业务应用

作者 | 百度智能小程序团队 导读 本文首先引入多利熊业务介绍&#xff0c;引出多利熊业务建设权限系统的痛点&#xff0c;接着分别从权限系统模型、权限系统设计以及多利熊业务业务应用方面详细探讨了具体的方案和设计&#xff0c;最后对权限系统设计思考&#xff0c;对数据维度…

linux连接器脚本前奏-基于x86(一)

从今天开始进入正文,和讲解liteos一样,我们先从连接器脚本开讲。我们知道连接器脚本描述了编译输出程序的布局,那么linux内核编译输出的布局是怎么样的呢?听我慢慢道来,关于连接器脚本的大概使用用途,可以参见 liteos链接器脚本一 liteos链接器脚本二 这里先说明一下对于…

Python进行异步请求,实现多开任务

前言 本文是该专栏的第5篇,后面会持续分享python的各种干货知识,值得关注。 在工作中,你可能或多或少会接到这样一个任务需求。 给你一个任务队列,需要你进行多任务去实现处理,尤其在爬虫项目或者是使用selenium,pyppeteer等任务中比较常见,至于多线程和多进程那些,笔…