观察者模式
-
- 观察者模式
-
- 主要组成部分
- 例一:工作流程
-
- 第一步:定义观察者接口
- 第二步:定义主题接口
- 第三步:实现具体主题
- 第四步:实现具体观察者
- 第五步:主函数
- [UML 图](#UML 图)
-
- [UML 图解析](#UML 图解析)
- 例二:工作流程
-
- 第一步:定义观察者接口
- 第二步:定义主题接口
- 第三步:实现具体主题(玩家类)
- 第四步:实现具体观察者(聊天通知器)
- 第五步:主函数
- [UML 图](#UML 图)
-
- [UML 图解析](#UML 图解析)
- 优缺点
- 适用场景
- 例一:完整代码
- 例二:完整代码
观察者模式
观察者模式(Observer Pattern)是一种常用的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象(主题)状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
引入"观察者"设计模式的定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。
主要组成部分
-
主题(Subject):
- 也叫观察目标,指被观察的对象。
- 维护观察者列表,提供添加、删除观察者的方法。
- 当状态变化时,通知所有的观察者。
-
观察者(Observer):
- 定义一个更新接口,以便主题在状态变化时调用。
- 每个观察者都可以根据主题的变化更新自身状态。
-
具体主题(ConcreteSubject):
- 实现主题的接口,维护具体的状态。
- 在状态变化时,调用通知方法来更新所有观察者。
-
具体观察者(ConcreteObserver):
- 实现观察者接口,更新自身状态以反映主题的变化。
- 每个观察者可以根据主题的状态执行特定的操作。
例一:工作流程
- 观察者注册到主题 :在主函数中,使用
attach
方法将观察者注册到主题。 - 主题的状态发生变化 :调用
setState
方法改变主题的状态。 - 主题调用通知方法 :在
setState
方法中,调用notify
方法通知所有注册的观察者。 - 观察者接收到通知后,更新自身状态 :每个观察者实现
update
方法,接收到通知后更新自身状态并输出。
第一步:定义观察者接口
首先,定义一个观察者接口,所有观察者都需要实现这个接口。
cpp
// 观察者接口
class Observer {
public:
virtual void update(int state) = 0; // 更新接口
};
第二步:定义主题接口
接下来,定义一个主题接口,主题需要维护观察者列表,并提供添加、删除观察者的方法。
cpp
// 主题接口
class Subject {
public:
virtual void attach(Observer* observer) = 0; // 添加观察者
virtual void detach(Observer* observer) = 0; // 移除观察者
virtual void notify() = 0; // 通知观察者
};
第三步:实现具体主题
实现一个具体主题类,维护观察者列表和状态,并在状态变化时通知观察者。
cpp
#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; // 获取当前状态
}
};
第四步:实现具体观察者
实现观察者类,更新自身状态以反映主题的变化。
cpp
// 具体观察者
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;
}
};
第五步:主函数
在主函数中,创建主题和观察者对象,注册观察者,并演示状态变化。
cpp
// 主函数
int main() {
ConcreteSubject subject; // 创建具体主题
ConcreteObserver observer1("Observer 1"); // 创建观察者1
ConcreteObserver observer2("Observer 2"); // 创建观察者2
subject.attach(&observer1); // 注册观察者1
subject.attach(&observer2); // 注册观察者2
subject.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
方法,接收到通知后输出聊天信息。
第一步:定义观察者接口
首先,定义一个观察者接口,所有玩家都需要实现这个接口,以接收聊天信息的更新通知。
cpp
// 观察者接口
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() {}
};
第二步:定义主题接口
接下来,定义一个定义主题接口,玩家需要维护聊天通知器,并提供添加、删除玩家的方法。
cpp
// 被观察者接口
class Fighter {
public:
virtual void NotifyWords(Fighter* talker, const std::string& message) = 0; // 更新接口
virtual void SetFamilyID(int id) = 0; // 设置家族ID
virtual int GetFamilyID() const = 0; // 获取家族ID
virtual ~Fighter() {}
};
第三步:实现具体主题(玩家类)
实现一个具体玩家类,维护玩家名称和家族ID,并在收到聊天信息时更新状态。
cpp
#include <iostream>
#include <string>
#include <list>
#include <map>
#include <algorithm> // 用于 std::remove
// 具体主题:玩家
class ConcreteFighter : public Fighter {
private:
std::string name; // 玩家名称
int familyID; // 家族ID
public:
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;
}
};
第四步:实现具体观察者(聊天通知器)
实现聊天通知器类,维护玩家列表,并在聊天内容变化时通知玩家。
cpp
// 具体观察者:聊天通知器
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); // 通知所有玩家
}
}
}
};
第五步:主函数
在主函数中,创建玩家和聊天通知器对象,注册玩家,并演示聊天信息的通知过程。
cpp
// 主函数
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(模型-视图-控制器)架构中,当模型数据变化时,视图需要自动更新以反映最新的数据。
-
发布-订阅系统:在消息传递系统中,多个订阅者会对特定主题的消息做出反应,发布者只需发布消息而不需要关心订阅者的具体实现。
-
状态监控:在监控系统中,多个观察者需要监控同一状态(如温度传感器的读数),当状态变化时,所有观察者都能及时获取更新。
-
社交媒体通知:当用户发布新内容时,所有关注该用户的粉丝会收到更新通知。
例一:完整代码
将以上步骤组合在一起,完整代码如下:
cpp
#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"); // 创建观察者1
ConcreteObserver observer2("Observer 2"); // 创建观察者2
subject.attach(&observer1); // 注册观察者1
subject.attach(&observer2); // 注册观察者2
subject.setState(1); // 状态变化,通知所有观察者
subject.setState(2); // 状态变化,通知所有观察者
return 0;
}
例二:完整代码
cpp
#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;
}