观察者模式
- 观察者模式
- 主要组成部分
- 例一:工作流程
- 第一步:定义观察者接口
- 第二步:定义主题接口
- 第三步:实现具体主题
- 第四步:实现具体观察者
- 第五步:主函数
- UML 图
- UML 图解析
- 例二:工作流程
- 第一步:定义观察者接口
- 第二步:定义主题接口
- 第三步:实现具体主题(玩家类)
- 第四步:实现具体观察者(聊天通知器)
- 第五步:主函数
- UML 图
- UML 图解析
- 优缺点
- 适用场景
- 例一:完整代码
- 例二:完整代码
观察者模式
观察者模式(Observer Pattern)是一种常用的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象(主题)状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
引入“观察者”设计模式的定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。
主要组成部分
-
主题(Subject):
- 也叫观察目标,指被观察的对象。
- 维护观察者列表,提供添加、删除观察者的方法。
- 当状态变化时,通知所有的观察者。
-
观察者(Observer):
- 定义一个更新接口,以便主题在状态变化时调用。
- 每个观察者都可以根据主题的变化更新自身状态。
-
具体主题(ConcreteSubject):
- 实现主题的接口,维护具体的状态。
- 在状态变化时,调用通知方法来更新所有观察者。
-
具体观察者(ConcreteObserver):
- 实现观察者接口,更新自身状态以反映主题的变化。
- 每个观察者可以根据主题的状态执行特定的操作。
例一:工作流程
- 观察者注册到主题:在主函数中,使用
attach
方法将观察者注册到主题。 - 主题的状态发生变化:调用
setState
方法改变主题的状态。 - 主题调用通知方法:在
setState
方法中,调用notify
方法通知所有注册的观察者。 - 观察者接收到通知后,更新自身状态:每个观察者实现
update
方法,接收到通知后更新自身状态并输出。
第一步:定义观察者接口
首先,定义一个观察者接口,所有观察者都需要实现这个接口。
// 观察者接口
class Observer {
public:virtual void update(int state) = 0; // 更新接口
};
第二步:定义主题接口
接下来,定义一个主题接口,主题需要维护观察者列表,并提供添加、删除观察者的方法。
// 主题接口
class Subject {
public:virtual void attach(Observer* observer) = 0; // 添加观察者virtual void detach(Observer* observer) = 0; // 移除观察者virtual void notify() = 0; // 通知观察者
};
第三步:实现具体主题
实现一个具体主题类,维护观察者列表和状态,并在状态变化时通知观察者。
#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove// 具体主题
class ConcreteSubject : public Subject {
private:std::vector<Observer*> observers; // 观察者列表int state; // 主题的状态public:void attach(Observer* observer) override {observers.push_back(observer); // 添加观察者}void detach(Observer* observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者}void notify() override {for (Observer* observer : observers) {observer->update(state); // 通知所有观察者}}void setState(int newState) {state = newState; // 更新状态notify(); // 状态变化时通知观察者}int getState() const {return state; // 获取当前状态}
};
第四步:实现具体观察者
实现观察者类,更新自身状态以反映主题的变化。
// 具体观察者
class ConcreteObserver : public Observer {
private:std::string name; // 观察者名称int observedState; // 观察者的状态public:ConcreteObserver(const std::string& name) : name(name) {}void update(int state) override {observedState = state; // 更新观察者的状态std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;}
};
第五步:主函数
在主函数中,创建主题和观察者对象,注册观察者,并演示状态变化。
// 主函数
int main() {ConcreteSubject subject; // 创建具体主题ConcreteObserver observer1("Observer 1"); // 创建观察者1ConcreteObserver observer2("Observer 2"); // 创建观察者2subject.attach(&observer1); // 注册观察者1subject.attach(&observer2); // 注册观察者2subject.setState(1); // 状态变化,通知所有观察者subject.setState(2); // 状态变化,通知所有观察者return 0;
}
UML 图
UML 图解析
Subject(主题):也叫作观察目标,指被观察的对象。这里指 Subject
类。提供增加和删除观察者对象的接口,如 attach
和 detach
。
ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify
向各个观察者发出通知。这里指 ConcreteSubject
类。
Observer(观察者):定义了观察者的接口,所有具体观察者都需要实现这个接口。当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Observer
类。
ConcreteObserver(具体观察者):实现了观察者接口,并注册到主题。当具体目标状态发生变化时,自身会接到通知。这里指 ConcreteObserver
类。
例二:工作流程
下面是逐步实现游戏聊天系统,使用观察者模式来管理玩家之间的聊天信息,每个玩家可以选择加入或退出家族聊天,系统会自动处理通知。将按照设计思路,逐步构建代码。
- 观察者注册到被观察者:在主函数中,使用
addToList
方法将玩家注册到聊天通知器。 - 被观察者的状态发生变化:调用
SayWords
方法改变聊天内容。 - 被观察者调用通知方法:在
SayWords
方法中,调用notify
方法通知所有注册的玩家。 - 观察者接收到通知后,更新自身状态:每个玩家实现
NotifyWords
方法,接收到通知后输出聊天信息。
第一步:定义观察者接口
首先,定义一个观察者接口,所有玩家都需要实现这个接口,以接收聊天信息的更新通知。
// 观察者接口
class Notifier {
public:virtual void addToList(Fighter* player) = 0; // 添加玩家virtual void removeFromList(Fighter* player) = 0; // 移除玩家virtual void notify(Fighter* talker, const std::string& message) = 0; // 通知玩家virtual ~Notifier() {}
};
第二步:定义主题接口
接下来,定义一个定义主题接口,玩家需要维护聊天通知器,并提供添加、删除玩家的方法。
// 被观察者接口
class Fighter {
public:virtual void NotifyWords(Fighter* talker, const std::string& message) = 0; // 更新接口virtual void SetFamilyID(int id) = 0; // 设置家族IDvirtual int GetFamilyID() const = 0; // 获取家族IDvirtual ~Fighter() {}
};
第三步:实现具体主题(玩家类)
实现一个具体玩家类,维护玩家名称和家族ID,并在收到聊天信息时更新状态。
#include <iostream>
#include <string>
#include <list>
#include <map>
#include <algorithm> // 用于 std::remove// 具体主题:玩家
class ConcreteFighter : public Fighter {
private:std::string name; // 玩家名称int familyID; // 家族IDpublic:ConcreteFighter(const std::string& playerName) : name(playerName), familyID(-1) {}void SetFamilyID(int id) override {familyID = id;}int GetFamilyID() const override {return familyID;}void NotifyWords(Fighter* talker, const std::string& message) override {std::cout << "玩家 " << name << " 收到了来自 " << talker->GetFamilyID() << " 的消息: " << message << std::endl;}
};
第四步:实现具体观察者(聊天通知器)
实现聊天通知器类,维护玩家列表,并在聊天内容变化时通知玩家。
// 具体观察者:聊天通知器
class TalkNotifier : public Notifier {
private:std::map<int, std::list<Fighter*>> familyList; // 家族ID与玩家列表的映射public:void addToList(Fighter* player) override {int familyID = player->GetFamilyID();if (familyID != -1) {familyList[familyID].push_back(player); // 添加玩家到对应家族}}void removeFromList(Fighter* player) override {int familyID = player->GetFamilyID();if (familyID != -1) {familyList[familyID].remove(player); // 移除玩家}}void notify(Fighter* talker, const std::string& message) override {int familyID = talker->GetFamilyID();if (familyID != -1) {for (Fighter* player : familyList[familyID]) {player->NotifyWords(talker, message); // 通知所有玩家}}}
};
第五步:主函数
在主函数中,创建玩家和聊天通知器对象,注册玩家,并演示聊天信息的通知过程。
// 主函数
int main() {// 创建聊天通知器TalkNotifier notifier;// 创建玩家ConcreteFighter player1("张三");player1.SetFamilyID(100);ConcreteFighter player2("李四");player2.SetFamilyID(100);ConcreteFighter player3("王五");player3.SetFamilyID(200);// 注册玩家到通知器notifier.addToList(&player1);notifier.addToList(&player2);notifier.addToList(&player3);// 玩家发送消息player1.SayWords("大家集合,准备进攻!", ¬ifier); // 张三发送消息player2.SayWords("听从指挥,前往目标!", ¬ifier); // 李四发送消息// 移除玩家notifier.removeFromList(&player2); // 移除李四// 再次发送消息player1.SayWords("李四已经不在了,继续行动!", ¬ifier); // 张三发送消息return 0;
}
UML 图
UML 图解析
Subject(主题):也叫作观察目标,指被观察的对象。这里指 Notifier
类。提供增加和删除观察者对象的接口,如 addToList
和 removeFromList
。
ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify
向各个观察者发出通知。这里指 TalkNotifier
子类。
Observer(观察者):当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Fighter
类。
ConcreteObserver(具体观察者):调用观察目标的 addToList
成员函数将自身加入到观察者列表中,当具体目标状态发生变化时,自身会接到通知(NotifyWords
成员函数会被调用)。这里指 F_Warrior
和 F_Mage
子类。
优缺点
优点:
- 松耦合:观察者和主题之间的关系是松散的,降低了模块间的耦合度。
- 动态添加观察者:可以在运行时增加或减少观察者。
- 多对一的通知:一个主题可以通知多个观察者,适合事件驱动的场景。
缺点:
- 通知开销:如果观察者数量较多,通知开销可能较大。
- 循环依赖:若观察者和主题相互依赖,可能导致循环更新。
适用场景
-
事件处理系统:GUI框架中,用户操作(如点击按钮)会触发事件,多个组件需要对这些事件做出反应。
-
数据绑定:在MVC(模型-视图-控制器)架构中,当模型数据变化时,视图需要自动更新以反映最新的数据。
-
发布-订阅系统:在消息传递系统中,多个订阅者会对特定主题的消息做出反应,发布者只需发布消息而不需要关心订阅者的具体实现。
-
状态监控:在监控系统中,多个观察者需要监控同一状态(如温度传感器的读数),当状态变化时,所有观察者都能及时获取更新。
-
社交媒体通知:当用户发布新内容时,所有关注该用户的粉丝会收到更新通知。
例一:完整代码
将以上步骤组合在一起,完整代码如下:
#include <iostream>
#include <vector>
#include <algorithm> // 用于 std::remove// 观察者接口
class Observer {
public:virtual void update(int state) = 0; // 更新接口
};// 主题接口
class Subject {
public:virtual void attach(Observer* observer) = 0; // 添加观察者virtual void detach(Observer* observer) = 0; // 移除观察者virtual void notify() = 0; // 通知观察者
};// 具体主题
class ConcreteSubject : public Subject {
private:std::vector<Observer*> observers; // 观察者列表int state; // 主题的状态public:void attach(Observer* observer) override {observers.push_back(observer); // 添加观察者}void detach(Observer* observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end()); // 移除观察者}void notify() override {for (Observer* observer : observers) {observer->update(state); // 通知所有观察者}}void setState(int newState) {state = newState; // 更新状态notify(); // 状态变化时通知观察者}int getState() const {return state; // 获取当前状态}
};// 具体观察者
class ConcreteObserver : public Observer {
private:std::string name; // 观察者名称int observedState; // 观察者的状态public:ConcreteObserver(const std::string& name) : name(name) {}void update(int state) override {observedState = state; // 更新观察者的状态std::cout << "Observer " << name << " updated with state: " << observedState << std::endl;}
};// 主函数
int main() {ConcreteSubject subject; // 创建具体主题ConcreteObserver observer1("Observer 1"); // 创建观察者1ConcreteObserver observer2("Observer 2"); // 创建观察者2subject.attach(&observer1); // 注册观察者1subject.attach(&observer2); // 注册观察者2subject.setState(1); // 状态变化,通知所有观察者subject.setState(2); // 状态变化,通知所有观察者return 0;
}
例二:完整代码
#include <iostream>
#include <list>
#include <map>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 tmpContent) = 0; //通知的一些细节信息virtual ~Notifier() {}
};
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;}int GetFamilyID() //获取家族ID{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
};
//“战士”类玩家,父类为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()){//该家族id在map中已经存在iter->second.push_back(player); //直接把该玩家加入到该家族}else{//该家族id在map中不存在list<Fighter*> tmpplayerlist;m_familyList.insert(make_pair(tmpfamilyid, tmpplayerlist));//以该家族id为key,增加条目到map中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);}}}//家族中某玩家说了句话,调用该函数来通知家族中所有人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; //增加#include <map>
};int main()
{//创建游戏玩家Fighter* pplayerobj1 = new F_Warrior(10, "张三");pplayerobj1->SetFamilyID(100);Fighter* pplayerobj2 = new F_Warrior(20, "李四");pplayerobj2->SetFamilyID(100);Fighter* pplayerobj3 = new F_Mage(30, "王五");pplayerobj3->SetFamilyID(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("请大家听从族长的调遣,前往沼泽地!", ptalknotify);//释放资源delete pplayerobj1;delete pplayerobj2;delete pplayerobj3;delete pplayerobj4;delete ptalknotify;return 0;
}