当前位置: 首页 > news >正文

C++ 设计模式——观察者模式

观察者模式

    • 观察者模式
      • 主要组成部分
      • 例一:工作流程
        • 第一步:定义观察者接口
        • 第二步:定义主题接口
        • 第三步:实现具体主题
        • 第四步:实现具体观察者
        • 第五步:主函数
        • UML 图
          • UML 图解析
      • 例二:工作流程
        • 第一步:定义观察者接口
        • 第二步:定义主题接口
        • 第三步:实现具体主题(玩家类)
        • 第四步:实现具体观察者(聊天通知器)
        • 第五步:主函数
        • UML 图
          • UML 图解析
      • 优缺点
      • 适用场景
      • 例一:完整代码
      • 例二:完整代码

观察者模式

观察者模式(Observer Pattern)是一种常用的设计模式,属于行为型模式。它定义了一种一对多的依赖关系,使得当一个对象(主题)状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

引入“观察者”设计模式的定义(实现意图):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会自动得到通知。

主要组成部分

  1. 主题(Subject)

    • 也叫观察目标,指被观察的对象。
    • 维护观察者列表,提供添加、删除观察者的方法。
    • 当状态变化时,通知所有的观察者。
  2. 观察者(Observer)

    • 定义一个更新接口,以便主题在状态变化时调用。
    • 每个观察者都可以根据主题的变化更新自身状态。
  3. 具体主题(ConcreteSubject)

    • 实现主题的接口,维护具体的状态。
    • 在状态变化时,调用通知方法来更新所有观察者。
  4. 具体观察者(ConcreteObserver)

    • 实现观察者接口,更新自身状态以反映主题的变化。
    • 每个观察者可以根据主题的状态执行特定的操作。

例一:工作流程

  1. 观察者注册到主题:在主函数中,使用 attach 方法将观察者注册到主题。
  2. 主题的状态发生变化:调用 setState 方法改变主题的状态。
  3. 主题调用通知方法:在 setState 方法中,调用 notify 方法通知所有注册的观察者。
  4. 观察者接收到通知后,更新自身状态:每个观察者实现 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 图一描述

UML 图解析

Subject(主题):也叫作观察目标,指被观察的对象。这里指 Subject 类。提供增加和删除观察者对象的接口,如 attachdetach

ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 ConcreteSubject 类。

Observer(观察者):定义了观察者的接口,所有具体观察者都需要实现这个接口。当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Observer 类。

ConcreteObserver(具体观察者):实现了观察者接口,并注册到主题。当具体目标状态发生变化时,自身会接到通知。这里指 ConcreteObserver 类。

例二:工作流程

下面是逐步实现游戏聊天系统,使用观察者模式来管理玩家之间的聊天信息,每个玩家可以选择加入或退出家族聊天,系统会自动处理通知。将按照设计思路,逐步构建代码。

  1. 观察者注册到被观察者:在主函数中,使用 addToList 方法将玩家注册到聊天通知器。
  2. 被观察者的状态发生变化:调用 SayWords 方法改变聊天内容。
  3. 被观察者调用通知方法:在 SayWords 方法中,调用 notify 方法通知所有注册的玩家。
  4. 观察者接收到通知后,更新自身状态:每个玩家实现 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("大家集合,准备进攻!", &notifier); // 张三发送消息player2.SayWords("听从指挥,前往目标!", &notifier); // 李四发送消息// 移除玩家notifier.removeFromList(&player2); // 移除李四// 再次发送消息player1.SayWords("李四已经不在了,继续行动!", &notifier); // 张三发送消息return 0;
}
UML 图

观察者模式 UML 图2

UML 图解析

Subject(主题):也叫作观察目标,指被观察的对象。这里指 Notifier 类。提供增加和删除观察者对象的接口,如 addToListremoveFromList

ConcreteSubject(具体主题):维护一个观察者列表,当状态发生改变时,调用 notify 向各个观察者发出通知。这里指 TalkNotifier 子类。

Observer(观察者):当被观察的对象状态发生变化时,观察者自身会收到通知。这里指 Fighter 类。

ConcreteObserver(具体观察者):调用观察目标的 addToList 成员函数将自身加入到观察者列表中,当具体目标状态发生变化时,自身会接到通知(NotifyWords 成员函数会被调用)。这里指 F_WarriorF_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;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【CAN总线测试】——CAN数据链路层测试
  • RK平台一个系统固件兼容多款屏幕
  • 虚幻5|AI行为树,跟随task(非行为树AI)
  • .NET应用UI框架DevExpress XAF v24.1 - 可用性进一步增强
  • 内存管理篇-03物理内存管理-32位
  • MySQL 的子查询(Subquery)
  • 单例模式 详解
  • 计算机毕业设计opencv+pytorch疲劳驾驶检测系统 自动驾驶 面部多信息特征融合的疲劳驾驶检测系统 驾驶员疲劳驾驶风险检测 深度学习 机器学习 大数据
  • Educational Codeforces Round 169 (Rated for Div. 2)
  • Java语言程序设计——篇十七(1)
  • verilog中两个常数相除
  • 三、LogicFlow 基础配置介绍及实现一个基础 Demo
  • Vue3 条件语句 8
  • <数据集>Visdrone数据集<目标检测>
  • Python编程:从入门到实践书籍介绍
  • 《剑指offer》分解让复杂问题更简单
  • GitUp, 你不可错过的秀外慧中的git工具
  • interface和setter,getter
  • java 多线程基础, 我觉得还是有必要看看的
  • JS实现简单的MVC模式开发小游戏
  • leetcode98. Validate Binary Search Tree
  • React的组件模式
  • spring boot 整合mybatis 无法输出sql的问题
  • 驱动程序原理
  • 如何合理的规划jvm性能调优
  • 仓管云——企业云erp功能有哪些?
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • $.ajax()方法详解
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (9)STL算法之逆转旋转
  • (day 12)JavaScript学习笔记(数组3)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (二十三)Flask之高频面试点
  • (回溯) LeetCode 77. 组合
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .gitignore文件—git忽略文件
  • .locked1、locked勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .NET框架
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • .Net中的设计模式——Factory Method模式
  • /ThinkPHP/Library/Think/Storage/Driver/File.class.php  LINE: 48
  • @取消转义
  • [ vulhub漏洞复现篇 ] Django SQL注入漏洞复现 CVE-2021-35042
  • [2016.7 day.5] T2
  • [20170713] 无法访问SQL Server
  • [2019.3.5]BZOJ1934 [Shoi2007]Vote 善意的投票
  • [autojs]autojs开关按钮的简单使用
  • [CQOI 2010]扑克牌
  • [Design Pattern] 工厂方法模式
  • [HOW TO]怎么在iPhone程序中实现可多选可搜索按字母排序的联系人选择器
  • [JavaScript]如何讓IE9, IE8, IE7, IE6關閉視窗時不彈出對話訊息
  • [Linux] Ubuntu install Miniconda
  • [Linux]于Mac在配置Linuxserver安装Nginx+PHP