观察者模式和订阅模式

观察者模式和订阅模式在概念上是相似的,它们都涉及到一个对象(通常称为“主题”或“发布者”)和多个依赖对象(称为“观察者”或“订阅者”)之间的关系。然而,尽管它们有相似之处,但在某些方面也存在细微的差别。

观察者模式(Observer Pattern)

‌核心思想‌

定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

‌结构‌

通常包括主题(Subject)和观察者(Observer)两个主要角色。主题维护一个观察者列表,当状态变化时,通知列表中的所有观察者。

‌实现方式‌

观察者模式可以通过在主题中维护一个观察者列表,并提供注册(addObserver)和注销(removeObserver)观察者的方法来实现。当主题状态变化时,遍历观察者列表并调用每个观察者的更新方法。

‌应用场景‌

常用于事件处理系统、GUI工具包中的事件监听器、订阅-发布系统等。

Demo

设计一个天气预报系统,当天气变化时,通知多个订阅了天气预报的用户。

定义 Subject 接口

Subject接口,被观察者,代表被观察的对象,定义注册新观察者,移除观察者,和通知观察者三个接口。

package org.example.observer;import java.util.Observer;public interface MySubject {void registerObserver(Observer o);void removeObserver(Observer o);void notifyObservers();
}

实现 Subject 接口

储存观察者列表,被观察对象变化信息,通知观察者列表

package org.example.observer;import java.util.ArrayList;
import java.util.List;public class WeatherStation implements MySubject {private List<MyObserver> observers;private String weather;public WeatherStation() {observers = new ArrayList<MyObserver>();}@Overridepublic void registerObserver(MyObserver o) {this.observers.add(o);}@Overridepublic void removeObserver(MyObserver o) {this.observers.remove(o);}@Overridepublic void notifyObservers() {this.observers.forEach(observer -> observer.update(weather));}public void setWeather(String weather) {this.weather = weather;notifyObservers();}
}

定义 Observer 接口

Observer接口,代表观察者,定义接收到通知后需要执行的动作接口

package org.example.observer;public interface MyObserver {void update(String weather);
}

实现 Observer 接口

观察者接收到通知后,实现具体要执行的动作

package org.example.observer;public class User implements MyObserver {private String name;public User(String name) {this.name = name;}@Overridepublic void update(String weather) {System.out.println(String.format("name %s receive weather update : %s", name, weather));}
}

测试

package org.example.observer;public class MyObserverMain {public static void main(String[] args) {// 创建被观察者对象WeatherStation station = new WeatherStation();// 创建观察者对象User userA = new User("A");User userB = new User("B");User userC = new User("C");// 注册观察者,并更新天气station.registerObserver(userA);station.registerObserver(userB);station.registerObserver(userC);station.setWeather("Sunny");// 移除部分观察者,再次更新天气station.removeObserver(userA);station.setWeather("Rainy");}
}

订阅模式(Subscription Pattern)

‌核心思想‌

也是一种一对多的关系,但更强调“订阅”的概念。订阅者订阅某个主题或频道,以接收该主题或频道发布的更新或消息。

‌结构‌

通常包括发布者(Publisher)、订阅者(Subscriber)和消息(Message)三个主要角色。订阅者通过订阅某个发布者来接收其发布的消息。

‌实现方式‌

订阅模式可以通过事件总线(Event Bus)、消息队列(Message Queue)或专门的订阅系统来实现。订阅者可以订阅特定的主题或频道,并接收该主题或频道发布的消息。

‌应用场景‌

广泛用于消息传递系统、事件驱动架构、分布式系统中的服务间通信等。

Demo

定义消息类

package org.example.publish_subscriber;public class Message {private String content;public Message(String content) {this.content = content;}public String getContent() {return content;}
}

定义订阅者接口

package org.example.publish_subscriber;public interface Subscriber {void receive(String message);
}

实现订阅者接口

package org.example.publish_subscriber;public class User implements Subscriber{private String name;public User(String name) {this.name = name;}@Overridepublic void receive(String message) {System.out.println(String.format("%s received message: %s", name, message));}
}

定义事件总线

package org.example.publish_subscriber;import java.util.Map;
import java.util.concurrent.*;public class EventBus {private final Map<Subscriber, BlockingQueue<Message>> subscriberQueues = new ConcurrentHashMap<>();private final ExecutorService executor = Executors.newCachedThreadPool();private volatile boolean running = true;public void subscriber(Subscriber subscriber) {BlockingQueue<Message> queue = new LinkedBlockingQueue<>();subscriberQueues.put(subscriber, queue);executor.submit(() -> {try {while (running || !queue.isEmpty()) {Message message = queue.take();subscriber.receive(message);}} catch (Exception e) {Thread.currentThread().interrupt();}});}public void publish(Message message) {subscriberQueues.values().forEach(queue -> {try {queue.put(message);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}public void shutdown() {running = false;executor.shutdown();try {if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {executor.shutdownNow();}} catch (InterruptedException e) {executor.shutdownNow();Thread.currentThread().interrupt();}}
}

测试

package org.example.publish_subscriber;public class PubSubMain {public static void main(String[] args) throws InterruptedException {EventBus eventBus = new EventBus();User userA = new User("A");User userB = new User("B");User userC = new User("C");User userD = new User("D");eventBus.subscriber(userA);eventBus.subscriber(userB);eventBus.subscriber(userC);eventBus.subscriber(userD);for (int i = 0; i < 10; i++) {eventBus.publish(new Message(i + "Hello World!"));eventBus.publish(new Message(i + "Hello Shore!"));}Thread.sleep(10000);eventBus.shutdown();}
}

在上面的例子中,executor.submit 被用于提交订阅者线程的任务。每个订阅者都有一个对应的工作队列,当发布消息时,消息会被放入每个订阅者的工作队列中。订阅者线程会不断地从自己的工作队列中取出消息并处理。通过这种方式,实现了发布-订阅模式中的异步通信和消息分发

区别与联系

‌区别‌

观察者模式更侧重于对象间的依赖关系和状态变化的通知机制;而订阅模式更强调消息的传递和订阅-发布的关系。此外,观察者模式通常是在单个应用或系统内使用;而订阅模式可能涉及跨系统或跨网络的消息传递。

‌联系‌

两者都涉及到一个对象(主题/发布者)和多个依赖对象(观察者/订阅者)之间的关系,且都实现了某种形式的通知机制。在某些情况下,它们可以相互替代或结合使用。例如,在一个分布式系统中,可以使用订阅模式来实现不同服务之间的消息传递,而在服务内部则可以使用观察者模式来实现状态变化的通知和更新。

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

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

相关文章

HarmonyOs鸿蒙开发实战(20)=>一文学会基础使用组件导航Navigation

敲黑板&#xff0c;以下是重点技巧。文章末尾有实战项目效果截图及代码截图可参考 1.概要 Navigation是路由导航的根视图容器Navigation组件主要包含​导航页&#xff08;NavBar&#xff09;和子页&#xff08;NavDestination&#xff09;&#xff0c;导航页不存在页面栈中&am…

Android12 的 Vold梳理

1.代码位置 system/vold/ 路径下,查看bp文件&#xff0c;发现是编译system/vold/main.cpp编译生成可执行文件vold 2.app侧调用代码流程 2.1 整体框架 #mermaid-svg-lqO8phN62rKNW407 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#…

DevOps-Jenkins-新手入门级

1. Jenkins概述 1. Jenkins是一个开源持续集成的工具&#xff0c;是由JAVA开发而成 2. Jenkins是一个调度平台&#xff0c;本身不处理任何事情&#xff0c;调用插件来完成所有的工作 1.1 什么是代码部署 代码发布/部署>开发书写的程序代码---->部署测试/生产环境 web服务…

11.22 深度学习-pytorch自动微分

# 自动微分模块torch.autograd负责自动计算张量操作的梯度&#xff0c;具有自动求导功能。自动微分模块是构成神经网络训练的必要模块&#xff0c;可以实现网络权重参数的更新&#xff0c;使得反向传播算法的实现变得简单而高效 import torch # 1. **张量** # Torch中一切…

在win10下搭建ftp服务器

1 说明 本文档在win10下实现。 2 安装ftp服务器 打开“控制面板/程序和功能”&#xff0c;如下&#xff1a; 点击“启用或关闭windows功能”&#xff0c;如下&#xff1a; 安装“ftp服务器”&#xff0c;将下图红色圈中部分打勾&#xff0c;如下&#xff1a; 必须勾选…

数据结构C语言描述4(图文结合)--栈的实现,中序转后序表达式的实现

前言 这个专栏将会用纯C实现常用的数据结构和简单的算法&#xff1b;有C基础即可跟着学习&#xff0c;代码均可运行&#xff1b;准备考研的也可跟着写&#xff0c;个人感觉&#xff0c;如果时间充裕&#xff0c;手写一遍比看书、刷题管用很多&#xff0c;这也是本人采用纯C语言…

对比 MyBatis 批处理 BATCH 模式与 INSERT INTO ... SELECT ... UNION ALL 进行批量插入

前言 在开发中&#xff0c;我们经常需要批量插入大量数据。不同的批量插入方法有不同的优缺点&#xff0c;适用于不同的场景。本文将详细对比两种常见的批量插入方法&#xff1a; MyBatis 的批处理模式。使用 INSERT INTO ... SELECT ... UNION ALL 进行批量插入。 MyBatis …

vue中路由缓存

vue中路由缓存 问题描述及截图解决思路关键代码及打印信息截图 问题描述及截图 在使用某一平台时发现当列表页码切换后点击某一卡片进入详情页后&#xff0c;再返回列表页时页面刷新了。这样用户每次看完详情回到列表页都得再重新输入自己的查询条件&#xff0c;或者切换分页到…

第N8周:使用Word2vec实现文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 本周任务&#xff1a; 结合Word2Vec文本内容预测文本标签 加载数据 import torch import torch.nn as nn import torchvision from torchvision import tra…

如何在 UniApp 中实现 iOS 版本更新检测

随着移动应用的不断发展&#xff0c;保持应用程序的更新是必不可少的&#xff0c;这样用户才能获得更好的体验。本文将帮助你在 UniApp 中实现 iOS 版的版本更新检测和提示&#xff0c;适合刚入行的小白。我们将分步骤进行说明&#xff0c;每一步所需的代码及其解释都会一一列出…

FreeRTOS之vTaskDelete实现分析

这里写自定义目录标题 1 函数接口1.1 函数接口1.2 函数参数简介 2 vTaskDelete的调用关系2.1 调用关系2.2 调用关系示意图 3 函数源码分析3.1 vTaskDelete3.2 uxListRemove 1 函数接口 1.1 函数接口 void vTaskDelete( TaskHandle_t xTaskToDelete )1.2 函数参数简介 TaskHa…

移动充储机器人“小奥”的多场景应用(上)

一、高速公路服务区应用 在高速公路服务区&#xff0c;新能源汽车的充电需求得到“小奥”机器人的及时响应。该机器人配备有储能电池和自动驾驶技术&#xff0c;能够迅速定位至指定充电点&#xff0c;为待充电的新能源汽车提供服务。得益于“小奥”的机动性&#xff0c;其服务…

C语言实例_5之根据输入年月日,计算属于该年的第几天

1. 题目 输入某年某月某日&#xff0c;判断这一天是这一年的第几天&#xff1f; 2. 分析 步骤1:得先判断年份是否是闰年&#xff0c;是的话&#xff0c;当月份大于3时&#xff0c;需多加一天&#xff1b; 步骤2:还需根据输入月份&#xff0c;判断输入天数是否合理&#xff0…

Semaphore 信号量

文章目录 基本概念工作原理Semaphore 与 ReentrantLockSemaphore常用场景1. 限制并发线程数&#xff08;最常见场景&#xff09;2. 公平模式的信号量&#xff08;保证按顺序访问资源&#xff09;3. 限制数据库连接数&#xff08;模拟数据库连接池&#xff09;4. 限制 API 请求次…

Redis 的代理类注入失败,连不上 redis

在测试 redis 是否成功连接时&#xff0c;发现 bean 没有被创建成功&#xff0c;导致报错 根据报错提示&#xff0c;需要我们添加依赖&#xff1a; <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>&l…

桌面怎么快速添加便签?适合桌面记事的便签小工具

在数字化时代&#xff0c;我们每天面对电脑处理大量任务&#xff0c;无论是工作计划、会议纪要还是个人生活琐事&#xff0c;都需要一个可靠的桌面记事工具来帮助我们记录和整理。因此&#xff0c;一款适合桌面使用的便签软件成为了我们不可或缺的助手。 敬业签就是这样一款功…

UE5 腿部IK 解决方案 footplacement

UE5系列文章目录 文章目录 UE5系列文章目录前言一、FootPlacement 是什么&#xff1f;二、具体实现 前言 在Unreal Engine 5 (UE5) 中&#xff0c;腿部IK&#xff08;Inverse Kinematics&#xff0c;逆向运动学&#xff09;是一个重要的动画技术&#xff0c;用于实现角色脚部准…

KLV6008固态继电器:高压应用的理想紧凑方案

在当今快节奏的电子领域&#xff0c;找到平衡性能、可靠性和安全性的组件至关重要。CRIA Semiconductor的KLV6008固态继电器(SSR)正是满足了这一要求。这款紧凑型继电器专为高压、低电流切换而设计&#xff0c;是适用于各种应用的多功能解决方案。 为什么选择KLV6008&#xff1…

如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!

在 React 项目中应用 TypeScript 是提升开发效率、增强代码可维护性和可读性的好方法。TypeScript 提供了静态类型检查、自动补全和代码提示等功能&#xff0c;这对于 React 开发者来说&#xff0c;能够帮助早期发现潜在的 bug&#xff0c;提高开发体验。 1. 项目初始化 在现…

解锁生成式AI的真实价值:衡量ROI的12步框架

在当今快速发展的技术环境中,生成式AI正逐渐成为企业创新和增长的重要驱动力。然而,随着数十亿美元的投资涌入生成式AI项目,一个严峻的问题浮出水面:如何衡量这些投资的回报(ROI)?本文将探讨生成式AI ROI衡量的挑战,并提供一个12步框架,帮助公司有效地评估和最大化其生…