设计模式10:观察者模式(订阅-发布)

 系列总链接:《大话设计模式》学习记录_net 大话设计-CSDN博客

参考:简说设计模式——工厂方法模式 - JAdam - 博客园

参考:简单工厂模式(Simple Factory Pattern) - 回忆酿的甜 - 博客园

一:概述        

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

        想象一下你订阅了一份杂志,当你订阅了这份杂志之后,每当有新一期出版时,杂志社就会自动给你寄一份过来。在这个例子中,你是“观察者”,而杂志社是“主题”。

把这个概念应用到软件开发中:

主题(Subject):就像是杂志社,它持有一份订阅名单(即观察者的列表)。当主题的状态发生变化或发生了某些重要的事情时,它会通知所有在名单上的观察者。
观察者(Observer):就像你和其他订阅者一样,观察者是对主题感兴趣的对象。它们注册到主题上,表示愿意接收更新。一旦主题有了新的信息,观察者们就会收到通知并作出相应的反应。
具体来说,如果你是一个用户,想要知道某个网站是否有新产品发布,你可以选择订阅该网站的邮件提醒服务。这个网站就是“主题”,而你的电子邮件客户端就是“观察者”。当网站发布了新产品,它会通过发送邮件的方式通知你(以及其他订阅了相同服务的用户),这样你就知道了有新产品可以购买。

特点:

解耦:你不需要知道是谁在管理这些邮件提醒,同样地,网站也不需要知道谁订阅了它的服务。双方只需要遵循一定的协议(如提供邮箱地址、点击订阅按钮等)。
灵活性:你可以随时取消订阅或者重新订阅,这意味着你可以动态地加入或离开观察者名单。
广播式的通知:只要有一个新产品发布,所有的订阅者都会同时收到通知,而不是一个接一个地通知。
事件驱动:这种模式非常适合用于那些需要对特定事件做出响应的情况,比如点击按钮、数据变化等。

二:结构与实现

结构:

借用下网络上的图:

来源:【设计模式系列】--观察者模式_观察者设计模式-CSDN博客

大抵分为:

观察者(订阅者):想要观察订阅某些主题的用户;

主题:也可叫频道,用来将消息数据分类,供用户观察订阅;

主题代理(发布者):管理主题和观察者(订阅者)关系的,当主题需发送消息时,由其向订阅者发送消息;

说明:

一般只要有个订阅类,发布者类即可,主题是在发布者类中进行定义,一般可以直接是字符串什么的。

实现:

mainwindow.ui:

mainwindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>//订阅者
class ISubscriber : public QObject
{Q_OBJECT
public:ISubscriber() {}virtual ~ISubscriber() {}virtual QString name() = 0;virtual void onMessage(QString channel, QString message) = 0;
};//发布者
class IPublisher : public QObject
{Q_OBJECT
public:IPublisher() {}virtual ~IPublisher() {}QHash<QString, QVector<ISubscriber*>> m_allSubscribers;void subscribe(QString channel, ISubscriber* subscriber){if(m_allSubscribers.contains(channel)){for(int i=0; i<m_allSubscribers.count(); i++){if(m_allSubscribers.value(channel).at(i) == subscriber){qDebug("Channel[%s] has been subscribed by subscriber[%s].",channel.toLatin1().data(), subscriber->name().toLatin1().data());return;}}m_allSubscribers[channel].append(subscriber);qDebug("Channel exist,  Subscriber[%s] subscribe channel[%s].",subscriber->name().toLatin1().data(), channel.toLatin1().data());}else{QVector<ISubscriber*> vector;vector.append(subscriber);m_allSubscribers.insert(channel, vector);qDebug("Channel not exist,  Subscriber[%s] subscribe channel[%s].",subscriber->name().toLatin1().data(), channel.toLatin1().data());}}void publish(QString channel, QString message){if(m_allSubscribers.contains(channel)){for(int i=0; i<m_allSubscribers.value(channel).count(); i++){m_allSubscribers.value(channel).at(i)->onMessage(channel, message);}}else{qDebug("Channel not exist,  publiser channel[%s] message no receiver.",channel.toLatin1().data());}}void unSubscribe(QString channel, ISubscriber* subscriber){if(m_allSubscribers.contains(channel)){for(int i=0; i<m_allSubscribers.count(); i++){if(m_allSubscribers.value(channel).at(i) == subscriber){m_allSubscribers[channel].remove(i);qDebug("Remove subscriber[%s] of channel[%s].",subscriber->name().toLatin1().data(),channel.toLatin1().data());return;}}}else{qDebug("unSubscribe, Channel not exist.",channel.toLatin1().data());}}
};class MySubscriber : public ISubscriber
{Q_OBJECT
public:MySubscriber(QString name) : m_name(name) {}virtual ~MySubscriber() {}QString name(){return m_name;}void onMessage(QString channel, QString message){qDebug("%s,name:%s, channel:%s, message:%s",Q_FUNC_INFO, m_name.toLatin1().data(),channel.toLatin1().data(), message.toLatin1().data());}private:QString m_name;
};QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private slots:void on_btn_sub1_clicked();void on_btn_sub2_clicked();void on_btn_pub_clicked();private:Ui::MainWindow *ui;MySubscriber* m_subscriber1;MySubscriber* m_subscriber2;IPublisher* m_publisher;
};
#endif // MAINWINDOW_H

mainwindow.cpp:

#include "MainWindow.h"
#include "ui_MainWindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);m_subscriber1 = new MySubscriber("sub1");m_subscriber2 = new MySubscriber("sub2");m_publisher = new IPublisher;
}MainWindow::~MainWindow()
{delete ui;if(m_subscriber1){m_subscriber1->deleteLater();}if(m_subscriber2){m_subscriber2->deleteLater();}if(m_publisher){m_publisher->deleteLater();}
}void MainWindow::on_btn_sub1_clicked()
{if(!ui->lineEdit_sub1->text().isEmpty()){m_publisher->subscribe(ui->lineEdit_sub1->text(), m_subscriber1);}
}void MainWindow::on_btn_sub2_clicked()
{if(!ui->lineEdit_sub2->text().isEmpty()){m_publisher->subscribe(ui->lineEdit_sub2->text(), m_subscriber2);}
}void MainWindow::on_btn_pub_clicked()
{if(!ui->lineEdit_pub->text().isEmpty()){m_publisher->publish(ui->lineEdit_pub->text(), ui->lineEdit_message->text());}
}

三:应用

1.Redis的订阅发布机制;

2.手机新闻订阅哪些频道的新闻,该频道出了新消息会推送出来;

四:优缺点及适用环境

优点:

解耦合:主题和观察者之间低耦合,主题不需要知道观察者的具体实现。
灵活性:可以动态添加或移除观察者,系统更灵活。
广播通信:适合用于需要向多个对象同时发送通知的情况。
 

缺点:

性能问题:大量观察者可能导致通知过程效率低下。
复杂性增加:可能引入不必要的复杂性,特别是当更新链很长时。
依赖管理:如果不妥善管理,可能会造成内存泄漏或循环依赖。
 

适用环境:

1.当一个对象的状态变化需要影响其他多个对象时。
2.适用于事件驱动系统,如GUI事件处理、消息发布/订阅系统等。
3.需要保持对象间的一致性但又不想直接耦合时。

简而言之,观察者模式非常适合用于需要维护对象间松散耦合并且能够方便地添加或删除依赖关系的场景。

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

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

相关文章

AIGC 时代的文学:变革与坚守

目录 一.AIGC 带来的文学变革 1.创作方式的改变 2.阅读体验的升级 3.文学市场的重塑 二.文学在 AIGC 时代的坚守 1.人类情感的表达 2.文学的艺术性 3.文学的社会责任 三.AIGC 与人类作家的共生之路 1.相互学习 2.合作创作 3.共同发展 另&#xff1a; 总结 随着人…

Wwise 使用MIDI文件、采样音频

第一种&#xff1a;当采样音频只有一个文件的时候 1.拖入MIDI文件到Interactive Music Hierarchy层级 2.拖入采样音频到Actor-Mixer Hierarchy层级 3.勾选MIDI显示出面板&#xff0c;设置Root Note与采样音频音高相同&#xff0c;这里是C#5 4.播放测试&#xff0c;成功&…

【计算机网络】实验9: 路由信息协议RIP

实验9 路由信息协议RIP 一、实验目的 本实验的主要目的是深入理解RIP&#xff08;路由信息协议&#xff09;的工作原理&#xff0c;以便掌握其在网络中的应用。通过对RIP的学习&#xff0c;我们将探讨该协议如何实现路由选择和信息传播&#xff0c;从而确保数据包能够在网络中…

python源码实例游戏开发小程序办公自动化网络爬虫项目开发源码(250+个项目、26.6GB)

文章目录 源代码下载地址项目介绍预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 python源码实例游戏开发小程序办公自动化网络爬虫项目开发源码(250个项目、26.6GB) 预览 项目备注 1、该资源内项目代码都经过测试运行成功&#xff0c;功能ok的情…

深入理解AVL树:结构、旋转及C++实现

1. AVL树的概念 什么是AVL树&#xff1f; AVL树是一种自平衡的二叉搜索树&#xff0c;其发明者是Adelson-Velsky和Landis&#xff0c;因此得名“AVL”。AVL树是首个自平衡二叉搜索树&#xff0c;通过对树的平衡因子进行控制&#xff0c;确保任何节点的左右子树高度差最多为1&…

spark-sql配置教程

1.前期准备 &#xff08;1&#xff09;首先要把hadoop集群&#xff0c;hive和spark等配置好 hadoop集群&#xff0c;hive的配置可以看看这个博主写的博客 大数据_蓝净云的博客-CSDN博客 或者看看黑马程序员的视频 黑马程序员大数据入门到实战教程&#xff0c;大数据开发必…

Git分布式版本控制工具 Git基本概念、Git工作流程、Git常用命令、Git远程仓库、IDEA操作Git

目录 ​​​​​​ 1.Git基本概念 1.1 概述 1.1.1 开发中的实际场景 1.1.2 版本控制器的方式 1.1.2.1 集中式版本控制工具(SVN) 1.1.2.2 分布式版本控制工具(Git) 2.概述git工作流程 3.Git常用命令 3.1 Git环境配置 3.1.1 下载与安装 3.1.2 基本配置 3.1.3 为常用指令配置别名&…

“停车费“ 在英语中常见的表达方式,柯桥职场英语生活口语商务英语学习

“停车费”用英语怎么说&#xff1f; "停车费" 在英语中有多种表达方式&#xff0c;最常见的是&#xff1a; Parking fee: 这是最直接的翻译&#xff0c;用于各种停车场、路边停车等情况。 Parking c15857575#376harge: 与 parking fee 意思相近&#xff0c;但有时更…

第31天:安全开发-JS应用WebPack打包器第三方库JQuery安装使用安全检测

时间轴&#xff1a; 演示案例&#xff1a; 打包器-WebPack-使用&安全 第三方库-JQuery-使用&安全 打包器-WebPack-使用&安全 参考&#xff1a;https://mp.weixin.qq.com/s/J3bpy-SsCnQ1lBov1L98WA Webpack 是一个模块打包器。在 Webpack 中会将前端的所有资源…

Redis使用场景-缓存-缓存雪崩

前言 之前在针对实习面试的博文中讲到Redis在实际开发中的生产问题&#xff0c;其中缓存穿透、击穿、雪崩在面试中问的最频繁&#xff0c;本文加了图解&#xff0c;希望帮助你更直观的了解缓存雪崩&#x1f600; &#xff08;放出之前写的针对实习面试的关于Redis生产问题的博…

【SARL】单智能体强化学习(Single-Agent Reinforcement Learning)《纲要》

&#x1f4e2;本篇文章是博主强化学习&#xff08;RL&#xff09;领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对相关等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅…

高通---Camera调试流程及常见问题分析

文章目录 一、概述二、Camera配置的整体流程三、Camera的代码架构图四、Camera数据流的传递五、camera debug FAQ 一、概述 在调试camera过程中&#xff0c;经常会遇到各种状况&#xff0c;本篇文章对camera调试的流程进行梳理。对常见问题的提供一些解题思路。 二、Camera配…

JAVA |日常开发中Servlet详解

JAVA &#xff5c;日常开发中Servlet详解 前言一、Servlet 概述1.1 定义1.2 历史背景 二、Servlet 的生命周期2.1 加载和实例化2.2 初始化&#xff08;init 方法&#xff09;2.3 服务&#xff08;service 方法&#xff09;2.4 销毁&#xff08;destroy 方法&#xff09; 三、Se…

网络(TCP)

目录 TCP socket API 详解 套接字有哪些类型&#xff1f;socket有哪些类型&#xff1f; 图解TCP四次握手断开连接 图解TCP数据报结构以及三次握手&#xff08;非常详细&#xff09; socket缓冲区以及阻塞模式详解 再谈UDP和TCP bind(): 我们的程序中对myaddr参数是这样…

JavaScript 键盘控制移动

如果你想通过 JavaScript 实现键盘控制对象&#xff08;比如一个方块&#xff09;的移动&#xff0c;下面是一个简单的示例&#xff0c;展示如何监听键盘事件并根据按下的键来移动一个元素。 HTML 和 CSS&#xff1a; <!DOCTYPE html> <html lang"en">…

图解SSL/TLS 建立加密通道的过程

众所周知&#xff0c;HTTPS 是 HTTP 安全版&#xff0c;HTTP 的数据以明文形式传输&#xff0c;而 HTTPS 使用 SSL/TLS 协议对数据进行加密&#xff0c;确保数据在传输过程中的安全。 那么&#xff0c;HTTPS 是如何做到数据加密的呢&#xff1f;这就需要了解 SSL/TLS 协议了。 …

自动化立体仓库项目任务调度系统中任务流程可视化实现

在运维自动化平台中,任务系统无疑是最核心的组成部分之一。它承担着所有打包编译、项目上线、日常维护等运维任务的执行。通过任务系统,我们能够灵活地构建满足不同需求的自定义任务流。早期的任务流后端采用了类似列表的存储结构,根据任务流内子任务的排序依次执行,尽管通…

【算法】【优选算法】位运算(下)

目录 一、&#xff1a;⾯试题 01.01.判定字符是否唯⼀1.1 位图1.2 hash思路1.3 暴力枚举 二、268.丢失的数字2.1 位运算&#xff0c;异或2.2 数学求和 三、371.两整数之和四、137.只出现⼀次的数字 II五、⾯试题 17.19.消失的两个数字 一、&#xff1a;⾯试题 01.01.判定字符是…

Java基础之GUI:探索图形化界面编程的魅力

一、引言 Java 的图形用户界面&#xff08;GUI&#xff09;编程为开发者提供了丰富的工具和组件&#xff0c;使得创建直观、交互性强的应用程序变得更加容易。本文将深入介绍 Java 基础中的 GUI&#xff0c;包括其概念、组件、布局管理器以及事件处理等方面的知识。 Java 的图…

极兔速递开放平台快递物流查询API对接流程

目录 极兔速递开放平台快递物流查询API对接流程API简介物流查询API 对接流程1. 注册用户2. 申请成为开发者3. 企业认证4. 联调测试5. 发布上线 签名机制详解1. 提交方式2. 签名规则3. 字段类型与解析约定 物流轨迹服务极兔快递单号查询的其他方案总结 极兔速递开放平台快递物流…