一个遍历问题导致的低效率范例
#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
- 观察者设计模式定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。
- 观察者模式也被称为 发布-订阅模式(Publish-Subscribe)。
- 观察者模式的四种角色:a) Subject(主题):观察目标,这里指的是Notifier类。b)ConcreteSubject(具体主题):这里指的是TalkNotifier类。c) Observer(观察者):这里指 Fighter类。d) ConcreteObserver(具体观察者):这里指 F_Warrior类和 F_Mage类。
- 观察者模式的特点: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;
}
程序运行结果为: