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

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

#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;
}

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

repinkply

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值