何为观察者模式?
在程序设计中经常会建立对象与对象,或者类与类之间的依赖关系,即当某一个对象发出通知或者发生变化的时候,会通知与他有关联的其他全部对象,就像在路口,当红灯变绿灯后,行人与汽车都会作出动作,实现一对多的联系。
代码:
做一个小小例子,玩游戏的家族频道,一人发言,所有同家族的成员都能收到消息,然后不同家族的成员无法收到消息,然后加入家族后可以收到消息,退出家族后收不到消息。
首先定义一个玩家属性:玩家自己的ID,玩家的昵称,玩家的所属家族,玩家的发言函数,收到对面玩家发言的函数。
class Fighter
{
public:
Fighter(int tmpID,string tmpName):m_iFamilyID(tmpID), m_sPlayerName(tmpName){
m_iFamilyID = -1; //表示未加入任何家族
}
~Fighter() {}
public:
void setFamilyID(int tmpID){
m_iFamilyID = tmpID;
}
int getFamilyID() {
return m_iFamilyID;
}
void sayWords(string tmpContent, Notifier* notifier)
{
notifier->notify(this, tmpContent);
}
//写成虚函数,方便子类覆盖
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
};
定义一个通知器类:用于管理玩家家族列表,例如加入成员,移除成员,和发言。
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() {}
};
多设计两个职业,继承于Fighter。
//战士
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 iter = m_familyList.find(tmpfamilyid); //在全部家族列表中找是不是存在该家族
if (iter != m_familyList.end()){
//该家族ID已经存在 直接将玩家加入到该家族中
iter->second.push_back(player);
}
else {
list<Fighter* >tmpplayerlist; //创建一个家族
m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist)); //然后将家族加入家族列表
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()){
m_familyList[tmpfamilyid].remove(player); //根据key值家族id移除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 iterlist = itermap->second.begin(); iterlist != itermap->second.end(); ++iterlist) {
(*iterlist)->notifyWords(talker, tmpContent);
}
}
}
}
private:
map<int, list<Fighter*>>m_familyList; //key 表示家族id value 表示家族中的成员列表list
};
在main函数中调用看看结果:
#include <iostream>
using namespace std;
#include "Observer.h"
int main()
{
Fighter* play01 = new F_Warrior(10, "张三");
play01->setFamilyID(100);
Fighter* play02 = new F_Warrior(20, "李四");
play02->setFamilyID(100);
Fighter* play03 = new F_Mage(30, "王五");
play03->setFamilyID(100);
Fighter* play04 = new F_Mage(40, "赵六");
play04->setFamilyID(200);
//创建通知器
Notifier* ptalkNotify = new talkNotifier();
//将玩家加入家族
ptalkNotify->addToList(play01);
ptalkNotify->addToList(play02);
ptalkNotify->addToList(play03);
ptalkNotify->addToList(play04);
//玩家发言
play01->sayWords("全体起立,准备战斗",ptalkNotify);
cout << "王五不想再收到其他成员信息---------------------------------------" << endl;
//移除王五
ptalkNotify->removeFromList(play03);
play02->sayWords("听从首领指挥,准备战斗", ptalkNotify);
//释放资源
delete play01;
delete play02;
delete play03;
delete play04;
return 0;
}
运行结果为:
UML图:
总结:
这个小游戏中主要用到两个容器,使用list容器管理家族成员,使用map容器管理家族,那么每次收发消息的时候,就遍历list容器,挨个调用函数,创造或者注销家族就在map容器中进行,每创建一个家族,就会对应创建家族list,便于管理。
观察者模式也被称作“发布-订阅”模式,只有提前订阅的人(在同一个list中)才能接收到发布者发布的消息。整个过程即为,发布者发布消息,通知器收到消息开始找订阅者,然后挨个给订阅者发送消息(即调用订阅者的成员函数)。
学习案例来源:《C++新经典设计模式》 王建伟编著