本文记录行为型设计模式之观察者模式(发布订阅模式)。
一个遍历问题导致的低效率范例
背景:之前的举得例子都是单机类游戏,现在公司为了增加游戏收入,将单机类游戏改为网络游戏,主要增加了两个功能:
功能1:游戏中游戏玩家必须实现游戏中玩家群体之间的战争,因为战争会消耗各种道具和物资,这些都要充钱买。为此,增加了家族概念,一个家族可容纳20人,不同家族的玩家之间可以根据游戏规则在指定的时间和地点通过战斗获利。
功能2:家族成员聊天,会被同家族的其他人看到。
cpp
namespace sp1
{
// 用一个全局list 存放都有玩家
class Fighter;
list<Fighter*> g_playerList;
class Fighter
{
public:
Fighter(int playerID, string playerName)
{
m_platerID = playerID;
m_playerName = playerName;
m_playerFamilyID = -1; // 初始值为-1,表示没有家族
}
virtual ~Fighter()
{
}
public:
// 加入某个家族,设置游戏玩家的家族ID
void joinFamily(int familyID)
{
m_playerFamilyID = familyID;
}
// 离开家族,设置游戏玩家的家族ID为-1
void leaveFamily()
{
m_playerFamilyID = -1;
}
// 游戏玩家说一句话
void sayWords(string msg)
{
if (m_playerFamilyID != -1)
{
// 该玩家有家族,通知家族成员
for (auto iter = g_playerList.begin(); iter != g_playerList.end(); ++iter)
{
if (m_playerFamilyID == (*iter)->m_playerFamilyID)
{
(*iter)->notifyFamily((*iter), msg);
}
}
}
}
private:
void notifyFamily(Fighter *otherPlayer,string msg)
{
// 通知家族成员
cout << "玩家 " << otherPlayer->m_playerName << "家族号:" << otherPlayer->m_playerFamilyID << " 说了" << msg << endl;
}
public: // 为了方便,这里把成员变量都设置为public
int m_platerID;
string m_playerName;
int m_playerFamilyID;
};
// 创建游戏玩家
class Warriar : public Fighter
{
public:
Warriar(int playerID, string playerName) : Fighter(playerID, playerName)
{
}
~Warriar()
{
}
};;
// 法师
class Magic : public Fighter
{
public:
Magic(int playerID, string playerName) : Fighter(playerID, playerName)
{
}
~Magic()
{
}
};;
}
int main()
{
// 创建有家族的玩家 1 2 3
sp1::Fighter *warriar1 = new sp1::Warriar(1, "战士1");
sp1::Fighter *warriar2 = new sp1::Warriar(2, "战士2");
sp1::Fighter *magic1 = new sp1::Magic(3, "法师");
warriar1->joinFamily(100);
warriar2->joinFamily(100);
magic1->joinFamily(100);
sp1::g_playerList.push_back(warriar1);
sp1::g_playerList.push_back(warriar2);
sp1::g_playerList.push_back(magic1);
sp1::Fighter *warriar3 = new sp1::Warriar(4, "战士3");
warriar3->joinFamily(200);
sp1::g_playerList.push_back(warriar3);
// 100家族的玩家warriar2说话
warriar2->sayWords("我是战士2,我要去打副本了");
/*
玩家 战士1家族号:100 说了我是战士2,我要去打副本了
玩家 战士2家族号:100 说了我是战士2,我要去打副本了
玩家 法师家族号:100 说了我是战士2,我要去打副本了
*/
std::cout << "Hello World!\n";
}
从上边代码可以看出,只要同家族的一个玩家说一句话之后,同家族的其他玩家就可以收到这句话。但是,这种方式效率并不好,因为同家族的某个玩家说一句话,就要遍历一遍所有的玩家列表,假如有10 0000个玩家,每说一句话就要遍历10万次,效率并不高,下面引入观察者模式改进这种效率低的方式。
引入观察者(Observer)模式
相比于在整个游戏玩家列表中遍历每个玩家,不如只遍历本家族玩家,提高了效率。
观察者设计模式定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知,比如下面示例中,同家族的玩家说话,家族的人都会知道。 pwarriar2->sayWords("我是战士2,我要去打副本了", pnotifier);
cpp
namespace sp2
{
class Fighter;
class Notifier // 通知器父类
{
public:
// 向家族中加入一个玩家
virtual void addToList(Fighter* player) = 0;
// 从家族列表中删除一个玩家
virtual void removeFromList(Fighter* player) = 0;
// 玩家说了话,通知家族成员
virtual void notifyFamily(Fighter* talker, string msg) = 0;
virtual ~Notifier()
{
}
};
class Fighter
{
public:
Fighter(int playerID, string playerName)
{
m_platerID = playerID;
m_playerName = playerName;
m_playerFamilyID = -1; // 初始值为-1,表示没有家族
}
virtual ~Fighter()
{
}
public:
// 加入某个家族,设置游戏玩家的家族ID
virtual void joinFamily(int familyID)
{
m_playerFamilyID = familyID;
}
// 离开家族,设置游戏玩家的家族ID为-1
virtual void leaveFamily()
{
m_playerFamilyID = -1;
}
// 获取家族ID
virtual int getFamilyID()
{
return m_playerFamilyID;
}
// 游戏玩家说一句话
virtual void sayWords(string msg, Notifier* notifier)
{
notifier->notifyFamily(this, msg);
}
public:
virtual void notifyWords(Fighter* otherPlayer, string msg)
{
// 通知家族成员
cout << "我是玩家 " << m_platerID << "收到了玩家 " << otherPlayer->m_playerName << " 家族号:" << otherPlayer->m_playerFamilyID << " 说了" << msg << endl;
}
public: // 为了方便,这里把成员变量都设置为public
int m_platerID;
string m_playerName;
int m_playerFamilyID;
};
// 创建游戏玩家
class Warriar : public Fighter
{
public:
Warriar(int playerID, string playerName) : Fighter(playerID, playerName)
{
}
~Warriar()
{
}
};;
// 法师
class Magic : public Fighter
{
public:
Magic(int playerID, string playerName) : Fighter(playerID, playerName)
{
}
~Magic()
{
}
};;
class NotifierMaster : public Notifier // 通知器父类
{
public:
// 向家族中加入一个玩家
void addToList(Fighter* player)
{
int tempFamilyID = player->getFamilyID();
if (tempFamilyID != -1)
{
map<int, list<Fighter*>>::iterator iter = m_mapFamilyList.find(tempFamilyID);
if (iter != m_mapFamilyList.end())
{
// 该家族已经存在,直接添加对应的家族list中
iter->second.push_back(player);
}
else
{
// 该家族不存在,创建家族
list<Fighter*> tempList;
tempList.push_back(player);
// m_mapFamilyList.insert(pair<int, list<Fighter*>>(tempFamilyID, tempList));
m_mapFamilyList.insert(make_pair(tempFamilyID, tempList));
}
}
}
// 从家族列表中删除一个玩家
void removeFromList(Fighter* player)
{
int tempFamilyID = player->getFamilyID();
if (tempFamilyID == -1)
{
return;
}
// 先查找是否存在
map<int, list<Fighter*>>::iterator iter = m_mapFamilyList.find(tempFamilyID);
if (iter != m_mapFamilyList.end())
{
m_mapFamilyList[tempFamilyID].remove(player);
}
}
// 玩家说了话,通知家族成员
void notifyFamily(Fighter* talker, string msg)
{
int tempFamilyID = talker->getFamilyID();
if (tempFamilyID == -1)
{
return;
}
// 先查找是否存在
map<int, list<Fighter*>>::iterator iter = m_mapFamilyList.find(tempFamilyID);
if (iter != m_mapFamilyList.end())
{
for (list<Fighter*>::iterator iter = m_mapFamilyList[tempFamilyID].begin(); iter != m_mapFamilyList[tempFamilyID].end(); ++iter)
{
(*iter)->notifyWords(talker, msg);
}
}
return;
}
private:
map<int, list<Fighter*>> m_mapFamilyList;
};
}
int main()
{
// 相比于 在整个游戏玩家列表中遍历每个玩家,不如只通知本家族玩家。
// 观察者设计模式 定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于
//它的对象都会自动得到通知。
sp2::Fighter* pwarriar1 = new sp2::Warriar(1, "战士1");
pwarriar1->joinFamily(100);
sp2::Fighter* pwarriar2 = new sp2::Warriar(2, "战士2");
pwarriar2->joinFamily(100);
sp2::Fighter* pwarriar3 = new sp2::Warriar(3, "战士2");
pwarriar3->joinFamily(100);
// 创建通知器
sp2::Notifier* pnotifier = new sp2::NotifierMaster();
/// 将玩家加入到通知器中
pnotifier->addToList(pwarriar1);
pnotifier->addToList(pwarriar2);
pnotifier->addToList(pwarriar3);
// 玩家2说话
pwarriar2->sayWords("我是战士2,我要去打副本了", pnotifier);
std::cout << "Hello World!\n";
}

观察者模式又叫 发布-订阅(Publish-Subscribe);
观察者模式四种角色:
a) Subject(主题):观察目标,这里指Notifier类。
b) ConcreteSubject(具体主题):这里指TalkNotifier类。
c) Observer(观察者):这里指Fighter类。
d) ConcreteObserver(具体观察者):这里指F_Warrior和F_Mage子类。
观察者模式的特点:
a)在观察者和观察目标之间建立了一个抽象的耦合
b)观察目标会向观察者列表中的所有观察者发送通知。
c)可以通过增加代码来增加新的观察者或者观察目标,符合开闭原则
应用联想
a) 救援家族成员镖车
b) 将新闻推荐给符合其胃口的读者
c) 通过改变自身绘制的图形来真实的反应公司的销售数据。
d) 炮楼只会对30米内的玩家(列表内玩家)进行攻击。
UML
