C++设计模式---观察者模式


观察者模式具体的应用情景

假设你还是游戏公司的程序员。策划想让你对游戏增加“家族”(或者“公会”)的功能,每个玩家都有一个家族id,家族id相同则代表属于同一个家族。

家族频道有聊天功能,只有同一个家族的人才能收到家族内部人员的信息。
你该如何实现这个功能呢?

首先我们想到的肯定是,用一个全局列表,也就是数组或者链表将所有玩家保存起来,当有一个人发送信息时,就去遍历整个全局列表,找到家族id相同的人,发给这个人信息。
说干就干,首先写出第一版代码:

#include <iostream>
#include <list>
using namespace std;
namespace hjl_project1
{
    class Fighter;
    // list保存所有玩家
    list<Fighter *> g_playList;
    class Fighter
    {
    public:
        Fighter(int tmpID, string tmpName)
            : m_iPlayerID(tmpID), m_sPlayerName(tmpName), m_iFamilyID(-1) //-1表示没有加入任何家族
        {
        }
        virtual ~Fighter() {}
        //设置家族id
        void SetFamilyID(int familyID)
        {
            m_iFamilyID = familyID;
        }
        //这个玩家发了一条信息
        void SayWords(string tmpContent)
        {
            //将这条信息传给同家族的所有玩家
            if (m_iFamilyID != -1)
            {
                for (auto iter : g_playList)
                {
                    if (iter->m_iFamilyID == this->m_iFamilyID)
                        NotifyWords(iter, tmpContent);
                }
            }
        }

    private:
        void NotifyWords(Fighter *otherPlayer, string tmpContent)
        {
            //显示信息
            cout << "玩家:" << otherPlayer->m_sPlayerName << "收到了玩家:" << m_sPlayerName << "的信息:" << tmpContent << endl;
        }
        int m_iPlayerID;
        string m_sPlayerName;
        //家族id
        int m_iFamilyID;
    };
    //战士类
    class F_Warrior : public Fighter
    {
    public:
        F_Warrior(int tmpID, string tmpName) : Fighter(tmpID, tmpName) {}
    };
    //法师类
    class F_Mage : public Fighter
    {
    public:
        F_Mage(int tmpID, string tmpName) : Fighter(tmpID, tmpName) {}
    };
}
int main()
{
    using namespace hjl_project1;
    Fighter *player1 = new F_Warrior(10, "张三");
    player1->SetFamilyID(100);     //假设该玩家的家族id是100
    g_playList.push_back(player1); //加入到全局列表中
    Fighter *player2 = new F_Warrior(11, "李四");
    player2->SetFamilyID(100); //假设该玩家的家族id是100
    g_playList.push_back(player2);
    Fighter *player3 = new F_Warrior(12, "王五");
    player3->SetFamilyID(101); //假设该玩家的家族id是101
    g_playList.push_back(player3);
    Fighter *player4 = new F_Warrior(13, "赵六");
    player4->SetFamilyID(100); //假设该玩家的家族id是100
    g_playList.push_back(player4);
    //当聊天室,某个玩家发送消息,同一个家族的人都会收到信息
    player1->SayWords("全家族的人立刻集合!");
}

在这里插入图片描述

上面的代码虽然实现了家族聊天的功能,但是效率并不高,因为每一次都要遍历全局列表,找到相同家族id的玩家,如果玩家数量很多,运行效率就会大打折扣。

下面引入观察者模式。
如果把所有的家族id相同的玩家收集到一个列表中,那发送信息时,就只需要遍历该玩家所在家族的列表,并向这个列表中所有的玩家发送消息即可。因为我们的“家族”是有上限的,比如一个“家族”只有100人,那么最多只需要遍历100个玩家即可。
我们使用一个通知器类来管理这个列表中的玩家。

namespace hjl_project2
{
    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;
    };

    class Fighter
    {
    public:
        Fighter(int tmpID, string tmpName)
            : m_iPlayerID(tmpID), m_sPlayerName(tmpName), m_iFamilyID(-1) //-1表示没有加入任何家族
        {
        }
        virtual ~Fighter() {}
        //设置家族id
        void SetFamilyID(int familyID)
        {
            m_iFamilyID = familyID;
        }
        //这个玩家发了一条信息
        void SayWords(string tmpContent, Notifier *notifier)
        {
            notifier->notify(this, tmpContent);
        }
        int GetFamilyID()
        {
            return m_iFamilyID;
        }
        //通知该玩家接收到其他玩家发送来的信息
        virtual void NotifyWords(Fighter *talkPlayer, string tmpContent)
        {
            //显示信息
            cout << "玩家:" << m_sPlayerName << "收到了玩家:" << talkPlayer->m_sPlayerName << "的信息:" << tmpContent << endl;
        }

    private:
        int m_iPlayerID;
        string m_sPlayerName;
        //家族id
        int m_iFamilyID;
    };
    //战士类
    class F_Warrior : public Fighter
    {
    public:
        F_Warrior(int tmpID, string tmpName) : Fighter(tmpID, tmpName) {}
    };
    //法师类
    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 itermap = m_familylist.find(tmpfamilyid);
                if (itermap != m_familylist.end())
                {
                    itermap->second.push_back(player);
                }
                else
                {
                    list<Fighter *> tmpplayerlist;
                    tmpplayerlist.push_back(player);
                    m_familylist.insert({tmpfamilyid, tmpplayerlist});
                }
            }
        }
        //将玩家从家族列表中删除
        virtual void removeFromList(Fighter *player)
        {
            int tmpfamilyid = player->GetFamilyID();
            if (tmpfamilyid != -1)
            {
                auto itermap = m_familylist.find(tmpfamilyid);
                if (itermap != m_familylist.end())
                {
                    m_familylist[tmpfamilyid].remove(player);
                }
            }
        }
        //家族中某个玩家说了一句话,调用通知函数来通知家族中所有的人
        virtual void notify(Fighter *talker, string tmpContent)
        {
            int tmpfamilyid = talker->GetFamilyID();
            if (tmpfamilyid != -1)
            {
                auto itermap = m_familylist.find(tmpfamilyid);
                if (itermap != m_familylist.end())
                {
                    //遍历该玩家所属家族的所有成员
                    for (auto iter : itermap->second)
                    {
                        iter->NotifyWords(talker, tmpContent);
                    }
                }
            }
        }

    private:
        //家族id,该家族id中所有玩家的列表
        map<int, list<Fighter *>> m_familylist;
    };
}
int main()
{
    using namespace hjl_project2;
    //创建通知器
    Notifier *ptalknotify = new TalkNotifier();

    Fighter *player1 = new F_Warrior(10, "张三");
    player1->SetFamilyID(100); //假设该玩家的家族id是100
    //将玩家增加到列表中来
    ptalknotify->addToList(player1);

    Fighter *player2 = new F_Warrior(11, "李四");
    player2->SetFamilyID(100); //假设该玩家的家族id是100
    ptalknotify->addToList(player2);

    Fighter *player3 = new F_Warrior(12, "王五");
    player3->SetFamilyID(101); //假设该玩家的家族id是101
    ptalknotify->addToList(player3);

    Fighter *player4 = new F_Warrior(13, "赵六");
    player4->SetFamilyID(100); //假设该玩家的家族id是100
    ptalknotify->addToList(player4);

    //某个玩家发送信息
    player1->SayWords("全家族的人立刻集合!", ptalknotify);
    //赵六退出了家族
    ptalknotify->removeFromList(player4);
    player1->SayWords("发起进攻!", ptalknotify);
}

在这里插入图片描述

在这里插入图片描述


观察者模式的定义

定义对象之间的一对多(“被观察者”和“观察者”)的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。也就是说,“被观察者”可以控制“观察者”的行为,类似于交通信号灯控制行人的行为。

比如上面的例子中,所有玩家都依赖于一个通知器,玩家每发送一条消息,所有依赖于这个通知器的玩家都会收到内容,通知器可以将这条消息显示地发送给同一个家族的玩家。

观察者模式也叫做发布-订阅模式

观察者模式中一般包含四种角色:

  1. Subject主题(观察目标),在上面的例子中指Notifier类,提供观察目标的接口(添加,删除,通知)。
  2. ConcreteSubject具体主题,维护一个观察列表,这里指TalkNotifier类。
  3. Observer观察者,这里指Fighter,当被观察者的状态发生变化时,观察者会收到通知。
  4. ConcreteOberver具体观察者,调用主题的接口将自己添加到观察列表中。

观察者模式的特点:

  1. 在观察者和观察目标之间建立了一个抽象的松耦合。观察目标只需要维持一个抽象的观察者列表,并不需要了解具体的观察者类。
  2. 观察目标会向观察列表中的所有观察者发送通知,而不是让观察者不断查询观察目标的状态,从而简化了一对多系统的设计难度。
  3. 可以增加代码的方式,增加观察者和观察目标,符合“开闭原则”。

观察者的应用联想:

  1. 还是以上面的例子,如果家族成员中某个人受到了攻击,就可以通知家族中的所有的人,请求他们前去支援。
  2. 实现推送的功能。比如一个博主发了一条视频,就可以用观察者模式将这篇新闻推送给关注这个博主的人。
  3. 某公司的产品数据由折线图、树状图、饼图等统计显示,如果产品数据发生了变化,就要通知这几种图形的观察者,从而修改这些图形的数据。
  4. 比如一个哨兵机器人会对附近30米之内的玩家进行攻击,哨兵机器人会维持一份列表来记录30米之内的玩家,因为玩家是不断移动的,所以这个列表中会不断被添加和删除玩家信息。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

今天也要写bug、

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

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

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

打赏作者

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

抵扣说明:

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

余额充值