1. 一个遍历问题导致的低效率范例
1.1 场景:
在线游戏中,多人聊天。同家族的所有成员可收到家族成员的聊天内容,非本家族不能收到。
1.2 示例代码
#pragma once#include <iostream>#include <string>#include <list>using namespace std;class Fighter;list<Fighter*> g_playerList;// 玩家父类(以往的战斗者类)class Fighter {public:Fighter(int playerID, string playerName): m_playerID(playerID), m_playerName(playerName){// -1 表示没有加入任何家族m_familyID = -1;}virtual ~Fighter() { }// 加入家族的时候要设置家族IDvoid setFamilyID(int familyID) {m_familyID = familyID;}// 玩家说了某句话void sayWords(string content) {// 该玩家属于某个家族,应该把聊天内容信息传送到给该家族的其他玩家for (auto iter = g_playerList.begin(); iter != g_playerList.end(); iter++) {if (m_familyID == (*iter)->m_familyID){// 同一个家族的其他玩家也应该收到聊天信息notifyWords(*iter, content);}}}private:void notifyWords(Fighter* otherPlayer, string content) {// 显示信息cout << otherPlayer->m_playerName << " receives the message sent by " << m_playerName << ", the chat message sent was" << content << endl;}private:// 玩家ID,全局唯一int m_playerID;// 玩家名字string m_playerName;// 家族IDint m_familyID;};// 战士类玩家,父类为Fighterclass Warrior :public Fighter {public:Warrior(int playerID, string playerName): Fighter(playerID, playerName){}};// 法师类玩家,父类为Fighterclass Mage :public Fighter {public:Mage(int playerID, string playerName): Fighter(playerID, playerName){}};void observer1() {// 创建游戏玩家(实际数据应来自数据库)Fighter* warrior1 = new Warrior(10, "fighter1");warrior1->setFamilyID(100);g_playerList.push_back(warrior1);Fighter* warrior2 = new Warrior(20, "fighter2");warrior2->setFamilyID(100);g_playerList.push_back(warrior2);Fighter* mage1 = new Mage(30, "mage1");mage1->setFamilyID(100);g_playerList.push_back(mage1);Fighter* mage2 = new Mage(40, "mage2");mage2->setFamilyID(200);g_playerList.push_back(mage2);// 当某个玩家聊天时,同族人都应该收到该信息warrior1->sayWords("The whole tribe immediately assembled in the marshes.");delete warrior1;delete warrior2;delete mage1;delete mage2;}
上述代码实现了该功能需求,但效率不高。sayWords这个函数中的for循环,如果玩家数量比较少的时候不受影响,但玩家数量增多至数万时就需要考虑效率问题,此时引入观察者模式。
2. 引入观察者模式
2.1 定义(实现意图)
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。观察者模式又称观察订阅模式。
2.2 示例代码
#pragma once#include <iostream>#include <list>#include <map>#include <string>using namespace std;class Fighter; // 类前向声明// 通知器父类class Notifier {public:// 把要被通知的玩家加入到列表中virtual void addToList(Fighter* player) = 0;// 把不想被通知的玩家从列表中去除virtual void removeFromList(Fighter* player) = 0;// 通知的一些细节信息virtual void notify(Fighter* talker, string comtent) = 0;virtual ~Notifier() { }};// 玩家父类(以往的战斗者类)class Fighter {public:Fighter(int playerID, string playerName): m_playerID(playerID), m_playerName(playerName){// -1 表示没有加入任何家族m_familyID = -1;}virtual ~Fighter() { }// 加入家族的时候要设置家族IDvoid setFamilyID(int familyID){m_familyID = familyID;}// 获取家族IDint getFamilyID(){return m_familyID;}// 玩家说了某句话void sayWords(Notifier* notifier, string content){notifier->notify(this, content);}// 通知该玩家接收到其他玩家发送来的聊天信息,虚函数子类可以覆盖以实现不同的功能void notifyWords(Fighter* talker, string content){// 显示信息cout << m_playerName << " receives the message sent by " << talker->m_playerName<< ", the chat message sent was" << content << endl;}private:// 玩家ID,全局唯一int m_playerID;// 玩家名字string m_playerName;// 家族IDint m_familyID;};// 战士类玩家,父类为Fighterclass Warrior : public Fighter {public:Warrior(int playerID, string playerName): Fighter(playerID, playerName){}};// 法师类玩家,父类为Fighterclass Mage : public Fighter {public:Mage(int playerID, string playerName): Fighter(playerID, playerName){}};// 聊天信息通知器class TalkNotifier : public Notifier {public:// 将玩家增加到家族列表中来void addToList(Fighter* player) override{int tempFamilyid = player->getFamilyID();if (tempFamilyid == -1) {return;}// 加入了某个家族auto iter = m_familyList.find(tempFamilyid);if (iter != m_familyList.end()) { // 家族ID存在iter->second.push_back(player);} else { // 家族ID不存在list<Fighter*> coll;m_familyList.insert(make_pair(tempFamilyid, coll));m_familyList[tempFamilyid].push_back(player);}}// 将玩家从家族列表中删除void removeFromList(Fighter* player) override{int tempFamilyid = player->getFamilyID();if (tempFamilyid == -1) {return;}auto iter = m_familyList.find(tempFamilyid);if (iter == m_familyList.end()) {return;}iter->second.remove(player);}// 家族中某玩家说了句话,调用该函数来通知家族中所有人void notify(Fighter* talker, string content) override{int tempFamilyid = talker->getFamilyID();if (tempFamilyid == -1) {return;}auto iter = m_familyList.find(tempFamilyid);if (iter == m_familyList.end()) {return;}list<Fighter*> coll = iter->second;// 遍历该家族的所有成员for (auto iterList = coll.begin(); iterList != coll.end(); ++iterList) {(*iterList)->notifyWords(talker, content);}}private:// key为家族ID, value代表该家族中所有玩家列表map<int, list<Fighter*>> m_familyList;};void observer1(){Fighter* warrior1 = new Warrior(10, "warrior1");warrior1->setFamilyID(100);Fighter* warrior2 = new Warrior(20, "warrior2");warrior2->setFamilyID(100);Fighter* mage1 = new Mage(30, "mage1");mage1->setFamilyID(100);Fighter* mage2 = new Mage(40, "mage2");mage2->setFamilyID(200);// 创建通知器Notifier* notifier = new TalkNotifier();// 玩家增加到家族列表中来,这样才能收到家族聊天记录notifier->addToList(warrior1);notifier->addToList(warrior2);notifier->addToList(mage1);notifier->addToList(mage2);// 当某游戏玩家聊天时,同族玩家都应该收到该信息string chatMsg1 = "The whole tribe immediately assembled in the marshes.";warrior1->sayWords(notifier, chatMsg1);cout << "Warrior2 doesn't want to receive any more chat messages from the rest of the family..." << endl;notifier->removeFromList(warrior2);string chatMsg2 = "Please follow the group leader's instructions.";mage1->sayWords(notifier, chatMsg2);delete warrior1;delete warrior2;delete mage1;delete mage2;delete notifier;}
2.3 类图
观察者模式的四种角色
- Subject(主题):观察目标,这里指Notifier类。
- ConcreteSubject(具体主题):这里指TalkNotifier类。
- Observer(观察者):这里指Fighter类。
- ConcreteObserver(具体观察者):这里指Warrior和Mage子类。
