设计模式之——观察者模式

一、观察者模式概述

        观察者模式是一种对象行为模式,它在软件设计中有着广泛的应用。这种模式定义了一种一对多的依赖关系,其中一个主题对象可被多个观察者对象同时监听。当主题对象的状态发生变化时,它会主动发出通知,使得所有依赖于它的观察者对象都能得到通知并被自动更新。

        例如,在图形用户界面编程中,一个数据模型可以作为主题,而多个界面组件可以作为观察者。当数据模型中的数据发生变化时,它会通知所有注册的观察者,这些观察者可以是文本框、下拉菜单、图表等界面组件。这些组件会根据数据的变化自动更新自己的显示内容。

        在业务对象之间的交互中,观察者模式也非常有用。比如,一个订单系统中,订单状态的变化可以作为主题,而物流系统、库存系统、客户通知系统等可以作为观察者。当订单状态发生变化时,比如从 “已下单” 变为 “已发货”,这些观察者系统会收到通知并进行相应的处理,如更新物流信息、减少库存数量、发送通知给客户等。

        观察者模式不仅被广泛应用于软件界面元素之间的交互,在权限管理等方面也有广泛的应用。例如,当用户的权限发生变化时,系统中的各个模块可以作为观察者,根据权限的变化进行相应的调整。

        总的来说,观察者模式通过定义一种一对多的依赖关系,实现了主题对象和观察者对象之间的解耦,使得系统更加灵活和可维护。

二、观察者模式的角色

(一)抽象主题

        抽象主题(Subject)是被观察的对象的抽象表示,它定义了一系列用于管理观察者的方法。通常,抽象主题会维护一个观察者列表,以便在状态发生变化时通知所有注册的观察者。

        例如,在一个新闻发布系统中,抽象主题可以是新闻源,它定义了添加、删除记者(观察者)的方法,以及通知记者有新新闻发布的方法。抽象主题的主要作用是提供一个统一的接口,使得具体主题可以方便地管理观察者,而不需要关心具体的观察者类型。

(二)具体主题

        具体主题(ConcreteSubject)是抽象主题的具体实现,它代表实际被观察的对象。具体主题通常会在内部状态发生变化时,调用通知方法通知所有注册的观察者。

        以电商平台为例,具体主题可以是商品库存系统。当商品库存数量发生变化时,库存系统会通知所有关注该商品的商家(观察者),以便商家及时调整销售策略。具体主题需要实现抽象主题中定义的方法,并负责维护自身的状态和观察者列表。

(三)抽象观察者

        抽象观察者(Observer)定义了观察者的通用接口,即响应通知的更新方法。这个方法会在具体主题状态发生变化时被调用,以便观察者可以根据变化做出相应的反应。

        比如在天气预报系统中,抽象观察者可以是各种气象设备,它们都需要实现一个更新方法,以便在接收到天气变化的通知时进行相应的处理,如调整显示数据、发送警报等。

(四)具体观察者

        具体观察者(ConcreteObserver)是抽象观察者的具体实现,它代表实际的观察者对象。具体观察者会在接收到具体主题的通知时,自动做出特定的响应。

        以股市行情系统为例,具体观察者可以是不同的投资者。当股票价格发生变化(具体主题状态变化)时,投资者会根据自己的投资策略做出相应的操作,如买入、卖出或持有股票。具体观察者需要实现抽象观察者中定义的更新方法,并根据具体的业务需求进行相应的处理。   

三、观察者模式的实现方式

(一)创建抽象主题角色

在 C++ 中,可以定义一个抽象主题类Subject,它包含一个观察者列表和一系列管理观察者的方法。以下是一个简单的实现:

#include <iostream>
#include <vector>
#include <string>
using namespace std;// 定义抽象基类 Subject,表示被观察的主题
class Subject {
public:// 纯虚函数,用于向主题添加一个观察者// 参数 observer 是指向 Observer 类型对象的指针,表示要添加的观察者// 由于该函数没有函数体且被声明为纯虚函数,所以任何继承自 Subject 的具体类都必须实现该函数virtual void addObserver(Observer* observer) = 0;// 纯虚函数,用于从主题中移除一个观察者// 参数 observer 同样是指向 Observer 类型对象的指针,表示要移除的观察者// 继承自 Subject 的具体类必须实现此函数,以提供移除观察者的具体逻辑virtual void removeObserver(Observer* observer) = 0;// 纯虚函数,用于通知所有已注册的观察者// 当主题状态发生变化或有事件发生时,调用此函数来通知观察者进行相应处理// 具体的通知方式和时机由继承自 Subject 的具体类来决定,因此该函数也是纯虚函数,需要在派生类中实现virtual void notifyObservers() = 0;private:// 定义一个私有成员变量 observers,用于存储所有已注册的观察者对象指针// 这里使用 std::vector 容器来管理观察者列表,方便添加、删除和遍历操作vector<Observer*> observers;
};

这个抽象主题类定义了添加观察者、删除观察者和通知观察者的纯虚函数,具体的实现将在具体主题类中完成。

(二)创建具体主题角色

具体主题类ConcreteSubject继承自抽象主题类Subject,实现了具体的业务逻辑和通知观察者的方法。例如:


// 定义抽象基类 Observer,它将由具体的观察者类继承并实现其抽象方法
class Observer 
{
public:// 纯虚函数,用于定义当被观察主题状态改变时观察者的更新行为// 具体的更新逻辑将在派生类中实现virtual void update() = 0;
};// 定义具体主题类 ConcreteSubject,它继承自抽象基类 Subject
class ConcreteSubject : public Subject 
{
public:// 实现抽象基类中的添加观察者方法// 将传入的观察者指针添加到观察者列表中void addObserver(Observer* observer) override {observers.push_back(observer);}// 实现抽象基类中的移除观察者方法// 遍历观察者列表,找到与传入指针相等的观察者并将其从列表中删除void removeObserver(Observer* observer) override {for (auto it = observers.begin(); it!= observers.end(); ++it) {if (*it == observer) {observers.erase(it);break;}}}// 实现抽象基类中的通知观察者方法// 遍历观察者列表,调用每个观察者的 update 方法,通知它们主题状态已改变void notifyObservers() override {for (auto observer : observers) {observer->update();}}// 设置主题的数据,并在数据更新后通知所有观察者void setData(int newData) {data = newData;notifyObservers();}private:// 存储主题相关的数据int data;// 存储所有注册到该主题的观察者指针vector<Observer*> observers;
};

在这个具体主题类中,实现了添加、删除观察者和通知观察者的方法。当数据发生变化时,通过调用setData方法,会通知所有注册的观察者。

(三)创建抽象观察者

抽象观察者类Observer定义了一个更新方法,具体观察者将实现这个方法来响应主题的状态变化。

// 定义抽象基类 Observer,用于表示观察者
class Observer 
{
public:// 纯虚函数 update,用于定义当被观察的主题状态发生变化时,观察者需要执行的更新操作// 具体的更新逻辑将由继承自 Observer 的具体观察者类来实现// 由于该函数是纯虚函数,所以 Observer 类不能被实例化,只能作为基类被继承virtual void update() = 0;
};

(四)创建具体观察者

具体观察者类ConcreteObserver实现了抽象观察者类的更新方法。例如:


// 定义具体观察者类 ConcreteObserver,继承自抽象基类 Observer
class ConcreteObserver : public Observer 
{
public:// 构造函数,用于初始化观察者对象的名称// 参数 name 是观察者的名称,将其存储在成员变量 observerName 中ConcreteObserver(const std::string& name) : observerName(name) {}// 实现抽象基类 Observer 中的纯虚函数 update// 当被观察的主题状态发生变化时,该函数会被主题调用,用于执行观察者的特定更新操作// 在这个例子中,它只是简单地输出一条消息,表明该观察者接收到了更新,并显示观察者的名称void update() override {std::cout << "Observer " << observerName << " received update." << std::endl;}private:// 存储观察者的名称string observerName;
};

(五)测试类验证

以下是一个测试类来验证观察者模式的实现:

int main() 
{// 创建一个具体主题对象 subjectConcreteSubject subject;// 创建两个具体观察者对象 observer1 和 observer2,并分别传入不同的名称进行初始化ConcreteObserver observer1("Observer 1");ConcreteObserver observer2("Observer 2");// 将 observer1 和 observer2 添加到 subject 的观察者列表中subject.addObserver(&observer1);subject.addObserver(&observer2);// 调用 subject 的 setData 方法,设置主题的数据,并触发通知观察者的操作subject.setData(10);return 0;
}

在业务中,比如在一个问答系统中,可以使用 JDK 封装的方法实现观察者模式。假设问题是抽象主题,回答者是观察者。当问题状态发生变化(如被编辑、有新的回答等)时,通知所有关注这个问题的回答者。具体实现可以类似如下:

1.定义问题类(抽象主题):

class Question {
public:virtual void addAnswerer(Answerer* answerer) = 0;virtual void removeAnswerer(Answerer* answerer) = 0;virtual void notifyAnswerers() = 0;
private:std::vector<Answerer*> answerers;
};

2.具体问题类:

class ConcreteQuestion : public Question {
public:void addAnswerer(Answerer* answerer) override {answerers.push_back(answerer);}void removeAnswerer(Answerer* answerer) override {for (auto it = answerers.begin(); it!= answerers.end(); ++it) {if (*it == answerer) {answerers.erase(it);break;}}}void notifyAnswerers() override {for (auto answerer : answerers) {answerer->update();}}void editQuestion() {// 问题被编辑,通知回答者notifyAnswerers();}
};

3.定义回答者类(抽象观察者):

class Answerer {
public:virtual void update() = 0;
};

4.具体回答者类:

class ConcreteAnswerer : public Answerer {
public:ConcreteAnswerer(const std::string& name) : answererName(name) {}void update() override {std::cout << "Answerer " << answererName << " received update about question change." << std::endl;}
private:std::string answererName;
};

4.测试:

int main() {ConcreteQuestion question;ConcreteAnswerer answerer1("Answerer 1");ConcreteAnswerer answerer2("Answerer 2");question.addAnswerer(&answerer1);question.addAnswerer(&answerer2);question.editQuestion();return 0;
}

四、观察者模式的应用场景

(一)事件驱动编程

        在事件驱动编程中,观察者模式是基础。许多框架和库都采用了这种模式,例如 Java Swing 和.NET WinForms 中的事件处理机制。当一个特定的事件发生时,如按钮被点击、鼠标移动等,相关的观察者会收到通知并执行相应的操作。这样可以实现代码的解耦,使得不同的功能模块可以独立开发和维护。

(二)界面组件与数据绑定

        在 GUI 编程中,界面组件通常需要与数据源进行绑定和同步更新。观察者模式可以很好地解决这种问题。以一个天气应用为例,当天气数据发生变化时,作为主题的天气数据源会通知作为观察者的界面组件,如温度显示标签、天气图标等,使其自动更新显示。这样可以确保用户界面始终显示最新的天气信息。

(三)消息订阅与发布

        在分布式系统或消息队列中,消息的订阅和发布可以使用观察者模式来实现解耦。例如,在一个微服务架构中,不同的服务可以作为观察者订阅特定的消息主题。当有新的消息发布到该主题时,所有订阅的服务都会收到通知并进行相应的处理。这样可以实现服务之间的异步通信和松耦合。

(四)属性绑定

        在一些富客户端应用程序中,属性之间的绑定可以使用观察者模式来实现。例如 JavaFX 中的属性绑定机制。当一个属性的值发生变化时,与之绑定的其他属性会自动更新。这样可以简化代码,提高开发效率。

(五)状态管理

        在复杂的状态管理场景中,观察者模式可以帮助管理状态变化的订阅和通知。例如 Redux 和 Vuex 等状态管理库。当应用的状态发生变化时,相关的组件会收到通知并更新自己的显示。这样可以确保用户界面始终与应用的状态保持一致。

(六)触发器和回调

        在系统中某个事件发生时,需要触发相应的回调函数。观察者模式可以方便地实现这种机制。例如,在一个文件上传系统中,当文件上传完成时,可以触发一个回调函数通知用户上传结果。这样可以提高系统的响应性和用户体验。

五、观察者模式的优缺点

(一)优点

  1. 解耦性良好:观察者模式将观察者和被观察者分离开来,两者之间仅通过抽象的接口进行交互。这使得它们之间的依赖关系变得松散,一个对象的变化不会直接影响到另一个对象,提高了系统的可维护性和可扩展性。例如在一个电商系统中,商品的库存变化是被观察者,而关注该商品的消费者可以作为观察者。当库存发生变化时,系统只需通知消费者,而无需关心消费者具体如何处理这个变化。
  2. 灵活性高:观察者模式允许在运行时动态地添加和删除观察者,使得系统能够灵活地适应不同的需求。比如在一个新闻发布系统中,新的用户可以随时订阅新闻,而已经订阅的用户也可以随时取消订阅,系统无需进行大规模的修改。
  3. 建立触发机制:观察者模式提供了一种有效的触发机制,当被观察者的状态发生变化时,能够自动通知所有的观察者。这种机制可以确保相关的对象及时得到更新,提高了系统的响应性。例如在一个股票交易系统中,当股票价格发生变化时,所有关注该股票的投资者都能及时收到通知。

(二)缺点

  1. 通知耗时:如果一个被观察者对象有很多直接和间接的观察者,那么在被观察者状态发生变化时,通知所有观察者会花费很多时间。以一个大型社交网络平台为例,当一个热门话题出现变化时,可能会有大量的用户作为观察者需要被通知,这可能会导致系统性能下降。
  2. 循环依赖风险:如果观察者和观察目标之间存在循环依赖关系,那么可能会导致系统崩溃。例如,在一个复杂的软件系统中,如果两个模块相互观察对方的状态变化,并且在某些情况下形成了循环调用,就可能引发严重的问题。
  3. 缺乏变化细节:观察者模式只通知观察者目标对象发生了变化,但没有提供相应的机制让观察者知道目标对象是怎么发生变化的。这在一些需要详细了解变化过程的场景下可能会带来不便。比如在一个数据分析系统中,观察者可能只知道数据发生了变化,但不知道具体是哪些数据发生了变化以及变化的原因。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/60219.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言中的 printf( ) 与 scanf( )

时隔多日&#xff0c;小编我又回来咯小编相信之前的博客能够给大家带来不少的收获。在我们之前的文章中&#xff0c;许多代码块的例子都用到了printf( ) 与 scanf( )这两个函数&#xff0c;大家都知道他们需要声明头文件之后才能使用&#xff0c;那这两个函数是什么呢&#xff…

数字乡村解决方案-1

1. 政策背景与新时代党建 党的十九大报告提出新时代党建总要求&#xff0c;强调乡村治理在国家治理体系中的重要性&#xff0c;并作为实现乡村振兴战略的基石。提出按照产业兴旺、生态宜居、乡风文明、治理有效、生活富裕的总要求&#xff0c;推进乡村治理体系和治理能力现代化…

栈和队列相关题 , 用队列实现栈, 用栈实现队列 ,设计循环队列 C/C++双版本

文章目录 1.用队列实现栈2.用栈实现队列3. 设计循环队列 1.用队列实现栈 225. 用队列实现栈 思路&#xff1a; 使用两个队列&#xff0c;始终保持一个队列为空。 当我们需要进行压栈操作时&#xff0c;将数据压入不为空的队列中&#xff08;若两个都为空&#xff0c;则随便压…

零基础入门进程间通信:task 1(匿名管道与vscode使用)

目录 引言 VSCODE使用 进程间通信正题 基础背景 进程间通信分类 匿名管道 理解匿名管道 代码实现 匿名管道的特性 管道的四种情况 应用场景 引言 在当今的计算机技术领域&#xff0c;操作系统作为计算机系统的核心组件&#xff0c;承担着资源管理、任务调度和进程管…

#渗透测试#SRC漏洞挖掘#Python自动化脚本的编写04之通过面向对象编程学生管理信息系统01

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

【销帮帮-注册_登录安全分析报告-试用页面存在安全隐患】

联通支付注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨…

微信小程序——01开发前的准备和开发工具

文章目录 一、开发前的准备1注册小程序账号2安装开发者工具 一、开发前的准备 开发前需要进行以下准备&#xff1a; 1 注册小程序账号2激活邮箱3 信息登记4 登录小程序管理后台5完善小程序信息6绑定开发者 1注册小程序账号 第1步&#xff1a;首先打开“微信公众平台” https:…

文心一言 VS 讯飞星火 VS chatgpt (388)-- 算法导论24.5 8题

八、设 G ( V , E ) G(V,E) G(V,E) 为一个带权重的有向图&#xff0c;且包含一个可以从源结点 s s s 到达的权重为负值的环路。请说明如何构造一个 G G G 的边的松弛操作的无限序列&#xff0c;使得每一步松弛操作都能对某一个最短路径估计值进行更新。如果要写代码&#x…

鸿蒙UI开发——自定义UI绘制帧率

1、概 述 随着设备屏幕的不断演进&#xff0c;当前主流设备采用LTPO屏幕&#xff08;可变刷新率屏幕&#xff09;&#xff0c;此类屏幕支持在多个档位之间切换屏幕帧率。 对于快速变化的内容&#xff0c;如射击游戏&#xff0c;交互动画等&#xff0c;显示帧率越高&#xff0…

计算机毕业设计 | SpringBoot慈善公益平台 爱心互助活动发布管理系统(附源码)

1&#xff0c;项目介绍 爱慈善公益平台&#xff08;love-charity&#xff09;是一个基于 SpringBoot 开发的标准 Java Web 项目。整体页面非常的简约大气&#xff0c;项目的完整度较高&#xff0c;是一个偏向公益论坛的系统。非常适合刚刚接触学习 SpringBoot 的技术小白学习&…

在 AMD GPU 上使用 AI2 的 OLMo 模型进行推理

Inferencing with AI2’s OLMo model on AMD GPU — ROCm Blogs 2024 年 4 月 17 日&#xff0c;作者&#xff1a;Douglas Jia. 在这篇博客中&#xff0c;我们将向您展示如何在 AMD GPU 上使用 AI2 的 OLMo 模型生成文本。 简介 由艾伦人工智能研究所&#xff08;Allen Instit…

工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置

工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置...-CSDN博客 工作流初始错误 泛微提交流程提示_泛微OA 工作流WebService接口使用说明 工作流初始错误 泛微提交流程提示_泛微OA 工作流WebService接口使用说明-CSDN博客 工作…

C++数学

前言 C算法与数据结构 打开打包代码的方法兼述单元测试 数论&#xff1a;质数、最大公约数、菲蜀定理 组合数学汇总 计算几何 博弈论 曼哈顿距离与切比雪夫距离 红线是哈曼顿距离&#xff0c;绿线是切比雪夫距离。 二维曼哈顿距离转切比雪夫距离 曼哈顿距离&#xff1a;|…

前深度学习时代-经典的推荐算法

参考自《深度学习推荐系统》—— 王喆&#xff0c;用于学习记录。 1.协同过滤 “协同过滤”就是协同大家的反馈、评价和意见一起对海量的信息进行过滤&#xff0c;从中筛选出目标用户可能感兴趣的信息的推荐过程。 基于用户相似度进行推荐的协同过滤算法 UserCF 用户相似度…

10 Oracle Data Guard:打造高可用性与灾难恢复解决方案,确保业务连续性

文章目录 10 Oracle Data Guard&#xff1a;打造高可用性与灾难恢复解决方案&#xff0c;确保业务连续性一、Data Guard基本概念二、Data Guard技术架构三、配置Oracle Data Guard的步骤3.1 准备主数据库和备用数据库3.2 配置Redo日志传输服务3.3 配置Data Guard Broker3.4 启动…

计算机网络综合题

IP数据报的划分 CRC差错检测 冗余码的计算 因此&#xff0c;余数是1110&#xff0c;传输的数为11010110111110。在传输过程中最后两位变成o&#xff0c;接收端能够发现&#xff0c;因为11010110111110除以10011余数不为0。 子网划分 暴力求解法 &#xff08;定长子网划分大量…

计算机课程管理:Spring Boot与工程认证的协同

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

Java | Leetcode Java题解之第557题反转字符串中的单词III

题目&#xff1a; 题解&#xff1a; class Solution {public String reverseWords(String s) {StringBuffer ret new StringBuffer();int length s.length();int i 0;while (i < length) {int start i;while (i < length && s.charAt(i) ! ) {i;}for (int …

C++ | Leetcode C++题解之第556题下一个更大元素III

题目&#xff1a; 题解&#xff1a; class Solution { public:int nextGreaterElement(int n) {int x n, cnt 1;for (; x > 10 && x / 10 % 10 > x % 10; x / 10) {cnt;}x / 10;if (x 0) {return -1;}int targetDigit x % 10;int x2 n, cnt2 0;for (; x2 …

第14张 GROUP BY 分组

一、分组功能介绍 使用group by关键字通过某个字段进行分组&#xff0c;对分完组的数据分别 “SELECT 聚合函数”查询结果。 1.1 语法 SELECT column, group_function(column) FROM table [WHERE condition] [GROUP BY group_by_expression] [ORDER BY column]; 明确&#…