当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
如今天我是在一个公会,需要通知公会里面的成员,那么此时我需要进行通知;但由于有多个公会,所以我们需要遍历其中的每一个人,然后进行通知;此时的效率就会低下;
下面代码中的Fighter就是我们的个体,当我们成员中发出SayWords,就是通知其他公会的成员,但此时由于g_playerList里面有不同的个体,他们来自不同的公会,此时需要遍历到大量的无用信息;如我们公会只有10人,但是总人数g_playerList却有100人,此时遍历的90人就是在浪费时间。
其中g_playerList存放着Fighter*,通过对象的指针,调用对应NotifyWords进行响应;
并且通知对象是由我们的对象进行负责,让通知对象的行为与对象本身强耦合了。如果在对象中维护不同的成员到list的关系,会使得代码过于臃肿。
namespace ljh1
{
class Fighter; //类前向声明
list<Fighter*> g_playerList;//记录所有成员的信息
//玩家父类(以往的战斗者类)
class Fighter
{
public:
Fighter(int tmpID, string tmpName) :m_iPlayerID(tmpID), m_sPlayerName(tmpName) //构造函数
{
m_iFamilyID = -1; //-1表示没有加入任何家族
}
virtual ~Fighter() {} //析构函数
public:
void SetFamilyID(int tmpID) //加入家族的时候要设置家族ID
{
m_iFamilyID = tmpID;
}
public:
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) {} //构造函数
};
}
观察者模式无非是引入了一个类Notifier,Notifier管理者成员以及他们的公会;如Notifier有一个成员变量map,记录着每一个成员ID与公会的人员列表的映射关系;那么当需要某一个成员需要通知时,只需要指定通知类Notifier,然后由Notifier将需要的信息,通过成员ID找到公会的人,并且此时的遍历都是有效遍历;
以下实现我们将Notifier定义为抽象类,TalkNotifier 继承 Notifier,因为我们观察对象的时候并不仅仅观察对象说什么,所以具体的工作由TalkNotifier来完成;Notifier 只定义最基本的纯虚函数如addToList(添加玩家入列表);removeFromList(从列表删除玩家);notify(进行信息的传递);
而我们的成员类(Fighter) 只需要定义 void SayWords(string tmpContent, Notifier* notifier)
,对比之前添加一个基类的Notifier指针,由调用者传入通知对象的指针,这样成员类(Fighter)不用关心如何通知,两者之间就是松耦合的关系;
namespace ljh2
{
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 tmpId,string tmpName):_id(tmpId), _playerName(tmpName)
{}
virtual ~Fighter() {} //析构
public:
void SetFamilyId(int tmpId) //加入家族的时候需要设置家族ID
{
_groupId = tmpId;
}
int GetFamilyId() //获取家族id
{
return _groupId;
}
public:
void SayWords(string tmpContent, Notifier* notifier)//玩家说了某句话
{
notifier->notify(this, tmpContent);
}
virtual void NotifyWords(Fighter* talker, string tmpContent)
{
cout << "玩家: " << _id << " 收到了玩家" << talker->_id << " 发送的聊天信息: " << tmpContent << endl;
}
private:
int _id; // 玩家ID
string _playerName; // 玩家姓名
int _groupId; //玩家所在组
};
//"战士"类玩家,父类为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)
{}
};
// Notifier 主要是维护了map,建立了从玩家id 到玩家家族集合所对应的映射
//然后负责进行通知方案
class TalkNotifier : public Notifier
{
public:
virtual void addToList(Fighter* player)
{
int tmpfamilyid = player->GetFamilyId();
if (tmpfamilyid != -1) //加入了某个家族
{
auto iter = _famliyList.find(tmpfamilyid);
//标识玩家player 有家族id
//此时有两种情况; 我是家族第一人,由我创建家族列表的第一项
if (iter == _famliyList.end())
{
//建立家族id 和 列表的映射
list<Fighter*> tmpplayerlist;// Fighter 的声明周期是main进行维护,一般来说是从数据库获取
_famliyList.insert(make_pair(tmpfamilyid,tmpplayerlist)); //以该家族id为key,增加条目到map
_famliyList[tmpfamilyid].push_back(player);
}
else // 我不是家族第一人
{
iter->second.push_back(player);//直接将玩家加入该家族;
}
}
}
//删除该玩家到列表
virtual void removeFromList(Fighter* player)
{
int tmpfamilyid = player->GetFamilyId();
if (tmpfamilyid != -1) // 加入了某个家族ID
{
auto iter = _famliyList.find(tmpfamilyid);
//找到就可以删了,找不到就不管了
if (iter != _famliyList.end())
{
_famliyList[tmpfamilyid].remove(player);
}
}
}
//家族中某玩家说了句话,调用该函数来通知家族中所有人
virtual void notify(Fighter* talker, string tmpContent)// talker 是讲话的玩家
{
int tmpfamilyid = talker->GetFamilyId();
if (tmpfamilyid != -1) // 当前说话的玩家加入了某个家族
{
auto iter = _famliyList.find(tmpfamilyid);
if (iter != _famliyList.end())
{
// 遍历该玩家所属家族的所有成员
for (auto iterlist = iter->second.begin(); iterlist != iter->second.end(); ++iterlist)
{
//每个玩家的迭代器
(*iterlist)->NotifyWords(talker,tmpContent);
}
}
}
}
private:
//map中的key 代表加入id,value 代表该家族中所在的玩家列表
map<int, list<Fighter*> > _famliyList;
};
}
main.cc 进行对接口的测试;定义玩家以及设置玩家所在公会,定义观察者对象,观察者将对象与所在工会建立映射;玩家(pplayerobj1)进行发送消息并且传观察者指针;由观察者进行对话的派发。
int main()
{
//创建游戏玩家
//这里的对象定义也可以从数据库当中获取
Fighter* pplayerobj1 = new F_Warrior(10,"张三"); // 玩家id,玩家姓名
pplayerobj1->SetFamilyId(100);// 假设家族ID 是 100
Fighter* pplayerobj2 = new F_Warrior(20, "李四");
pplayerobj2->SetFamilyId(100);// 假设家族ID 是 100
Fighter* pplayerobj3 = new F_Mage(30, "王五");
pplayerobj3->SetFamilyId(100);// 假设家族ID 是 100
Fighter* pplayerobj4 = new F_Mage(50, "赵六");
pplayerobj4->SetFamilyId(200); //赵六和前面三人属于两个不同的家族
//创建通知器
Notifier* ptalknotify = new TalkNotifier();
//玩家增加到家族列表中来,这样才能收家族聊天信息
ptalknotify->addToList(pplayerobj1);
ptalknotify->addToList(pplayerobj2);
ptalknotify->addToList(pplayerobj3);
ptalknotify->addToList(pplayerobj4);
//某游戏玩家聊天,同族人都会收到该消息
pplayerobj1->SayWords("全体人员向我看齐,我宣布个事", ptalknotify);
cout << "王五没有退出家族,但是屏蔽了家族的来信" << endl;
ptalknotify->removeFromList(pplayerobj3);// 把王五从观察者中进行删除
pplayerobj2->SayWords("王五退出了,一起diss他", ptalknotify);
//释放资源
delete pplayerobj1;
delete pplayerobj2;
delete pplayerobj3;
delete pplayerobj4;
delete ptalknotify;
return 0;
}
优点:
1、观察者和被观察者是抽象耦合的。
2、建立一套触发机制。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
参考:
https://www.runoob.com/design-pattern/observer-pattern.html