对观察者模式的理解

目录

  • 一、场景
    • 1、题目描述 【[案例来源](https://kamacoder.com/problempage.php?pid=1075)】
    • 2、输入描述
    • 3、输出描述
    • 4、输入示例
    • 5、输出示例
  • 二、实现
  • 三、更复杂的场景 【[案例来源](https://refactoringguru.cn/design-patterns/observer/java/example#example-0--listeners-EmailNotificationListener-java)】
    • 1、简单实现
      • 1.1 可以改进的地方
    • 2、更优雅的实现
  • 四、个人思考

一、场景

  • 观察者模式是行为型模式之一。
    • 类与类之间如何协作才算观察者模式呢?
  • 试想一下:
    • 学生们坐在教室上课,到了下课时刻,下课铃声响起,学生们听到铃声,进入课间休息时段。
      • 学生是观察者,观察对象是铃声通知器(一般挂在教室门口的墙上)。
  • 这种协作便是观察者模式。

1、题目描述 【案例来源】

小明所在的学校有一个时钟(主题),每到整点时,它就会通知所有的学生(观察者)当前的时间,请你使用观察者模式实现这个时钟通知系统。
注意点:时间从 1 开始,并每隔一个小时更新一次。

2、输入描述

输入的第一行是一个整数 N(1 ≤ N ≤ 20),表示学生的数量。
接下来的 N 行,每行包含一个字符串,表示学生的姓名。
最后一行是一个整数,表示时钟更新的次数。

3、输出描述

对于每一次时钟更新,输出每个学生的姓名和当前的时间。

4、输入示例

2
Alice
Bob
3

5、输出示例

Alice 1
Bob 1
Alice 2
Bob 2
Alice 3
Bob 3

二、实现

  • 主题(Subject): 铃声通知器

主题状态(数据)变化后,通知订阅了该主题的观察者。

亦称:发布者(Publisher)
发布者状态(数据)变化后,通知订阅者(Subscriber)/观察者。

public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}public class TimeSubject implements Subject {private List<Observer> observers;@Setterprivate Integer startTime;public TimeSubject(Integer startTime) {this.startTime = startTime;this.observers = new ArrayList<>();}@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(startTime);}startTime = (startTime + 1) % 24;}
}
  • 观察者/订阅者
public interface Observer {void update(Integer currentTime);
}public class StudentObserver implements Observer {private String name;public StudentObserver(String name) {this.name = name;}@Overridepublic void update(Integer currentTime) {System.out.println(name + " " + currentTime);}
}
  • 客户端
public class Application {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);TimeSubject timeSubject = new TimeSubject(1);int n = scanner.nextInt();for (int i = 0; i < n; i++) {String name = scanner.next();Observer studentObserver = new StudentObserver(name);timeSubject.registerObserver(studentObserver);}int frequency = scanner.nextInt();for (int i = 0; i < frequency; i++) {timeSubject.notifyObservers();}}

三、更复杂的场景 【案例来源】

  • 编辑器提供两种功能:打开文件 和 保存文件。
    • 当打开文件时,发邮件通知监听该行为的观察者。
    • 当保存文件时,会触发打印日志。

分析:

  • 客户端使用编辑器,当出现“打开文件”或者“保存文件”时,触发发送邮件或打印日志。
    • 很显然,编辑器是“铃声通知器”,是发布者。邮件监听、日志监听是观察者。

1、简单实现

  • 发布者
public interface Publisher {void registerObserver(String observeType, Observer observer);void removeObserver(String observeType, Observer observer);void notifyObservers(String observeType);
}public class Editor implements Publisher {private List<Observer> emailObservers;private List<Observer> logObservers;private File file;public Editor() {emailObservers = new ArrayList<>();logObservers = new ArrayList<>();}@Overridepublic void registerObserver(String observeType, Observer observer) {if ("open file".equals(observeType)) {emailObservers.add(observer);} else if ("save file".equals(observeType)) {logObservers.add(observer);} else {throw new RuntimeException("invalid observeType");}}@Overridepublic void removeObserver(String observeType, Observer observer) {if ("open file".equals(observeType)) {emailObservers.remove(observer);} else if ("save file".equals(observeType)) {logObservers.remove(observer);} else {throw new RuntimeException("invalid observeType");}}@Overridepublic void notifyObservers(String observeType) {ObserveContext observeContext = new ObserveContext().setObserveType(observeType).setFile(file);if ("open file".equals(observeType)) {emailObservers.stream().forEach(observer -> observer.update(observeContext));} else if ("save file".equals(observeType)) {logObservers.stream().forEach(observer -> observer.update(observeContext));} else {throw new RuntimeException("invalid observeType");}}public void openFile(String filePath) {if (StringUtils.isEmpty(filePath)) {throw new RuntimeException("filePath is empty");}this.file = new File(filePath);notifyObservers("open file");}public void saveFile() {if (null == this.file) {throw new RuntimeException("file is null");}notifyObservers("save file");}
}
  • 订阅者
public interface Observer {void update(ObserveContext context);
}public class EmailObserver implements Observer {private String email;public EmailObserver(String email) {this.email = email;}@Overridepublic void update(ObserveContext context) {String observeType = context.getObserveType();File file = context.getFile();Objects.requireNonNull(observeType, "observeType must not be null");Objects.requireNonNull(file, "file must not be null");System.out.println("send email to " + email + ", content: " + observeType + " " + file.getName());}
}public class LogObserver implements Observer {private String logFilePath;public LogObserver(String logFilePath) {this.logFilePath = logFilePath;}@Overridepublic void update(ObserveContext context) {String observeType = context.getObserveType();File file = context.getFile();Objects.requireNonNull(observeType, "observeType must not be null");Objects.requireNonNull(file, "file must not be null");System.out.println("save log to " + logFilePath + ", content: " + observeType + " " + file.getName());}
}
  • 发布者-订阅者,交互的数据
@Data
@Accessors(chain = true)
public class ObserveContext {private String observeType;private File file;
}

打开文件 or 保存文件

  • 客户端
public class Application {public static void main(String[] args) {Editor editor = new Editor();editor.registerObserver("open file", new EmailObserver("forrest@qq.com"));editor.registerObserver("save file", new LogObserver("/user/forrest/log/editor_log.txt"));editor.openFile("/user/forrest/file/test.txt");editor.saveFile();}
}/*
send email to forrest@qq.com, content: open file test.txt
save log to /user/forrest/log/editor_log.txt, content: save file test.txt
*/

1.1 可以改进的地方

  • Editor不符合单一原则。
    • 既有与编辑器相关的打开文件/关闭文件的API,又有与编辑器不相关的发布者逻辑。
  • 解决办法:对发布者逻辑进行封装。

2、更优雅的实现

  • 发布者
public interface Publisher {void registerObserver(String observeType, Observer observer);void removeObserver(String observeType, Observer observer);void notifyObservers(ObserveContext context); // 这里从`String observeType`变成了`ObserveContext context`(更灵活)
}// 对发布者逻辑进行封装。
public class PublisherManager implements Publisher {private static final Map<String, List<Observer>> OBSERVER_MAP = new HashMap<>();@Overridepublic void registerObserver(String observeType, Observer observer) {List<Observer> observerList = OBSERVER_MAP.get(observeType);if (observerList == null) {observerList = new ArrayList<>();OBSERVER_MAP.put(observeType, observerList);}observerList.add(observer);}@Overridepublic void removeObserver(String observeType, Observer observer) {List<Observer> observerList = OBSERVER_MAP.get(observeType);if (observerList != null) {observerList.remove(observer);}}@Overridepublic void notifyObservers(ObserveContext context) {List<Observer> observerList = OBSERVER_MAP.get(context.getObserveType());if (observerList != null) {for (Observer observer : observerList) {observer.update(context);}}}
}
  • 订阅者 + ObserveContext(和之前没变化)
    • 发布者和订阅者通过接口基于context进行交互,两者的耦合度极低。当发布者重构了,订阅者是无感知的。
  • Editor(组合了Publisher接口,逻辑很纯粹)
public class Editor {private static final Publisher publisherManager = new PublisherManager();private File file;public static Publisher getPublisherManager() {return publisherManager;}public void openFile(String filePath) {if (StringUtils.isEmpty(filePath)) {throw new RuntimeException("filePath is empty");}this.file = new File(filePath);ObserveContext observeContext = new ObserveContext().setObserveType("open file").setFile(file);publisherManager.notifyObservers(observeContext);}public void saveFile() {if (null == this.file) {throw new RuntimeException("file is null");}ObserveContext observeContext = new ObserveContext().setObserveType("save file").setFile(file);publisherManager.notifyObservers(observeContext);}
}
  • 客户端:
public class Application {public static void main(String[] args) {Editor editor = new Editor();Editor.getPublisherManager().registerObserver("open file", new EmailObserver("forrest@qq.com"));Editor.getPublisherManager().registerObserver("save file", new LogObserver("/user/forrest/log/editor_log.txt"));editor.openFile("/user/forrest/file/test.txt");editor.saveFile();}
}/*
send email to forrest@qq.com, content: open file test.txt
save log to /user/forrest/log/editor_log.txt, content: save file test.txt
*/

四、个人思考

  • 像“一、场景”中纯粹的发布者应该不多见,更常见的应该是“三、更复杂的场景”:一个应用,当其某些状态改变时(文件的打开或者关闭)会触发其他行为。这是观察者模式的用武之地。

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

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

相关文章

深入OceanBase内部机制:资源隔离实现的方式总结

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 1. 为何HTAP需要资源隔离2. OceanBase的资源隔离机制概述租户间资源隔离租户内资源隔离物理资源隔离大查询请求的隔离优先级…

26.ELF文件解析

ELF文件及objdump/readelf命令 文章目录 ELF文件及objdump/readelf命令ELF文件结构分析使用od命令读取ELF文件使用readelf命令读取ELF文件使用objdump命令分析ELF文件reference 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; ELF(Executa…

4月21敲一篇猜数字游戏,封装函数,void,无限循环,快去体验体验

今天敲一篇猜数字游戏 目录 今天敲一篇猜数字游戏 1.打开先学goto语句&#xff1a; 2.开干&#xff1a; 首次我们学习随机数&#xff1a; 讲解一下&#xff1a; 改用srand; 加入时间变量&#xff1a; 获取时间&#xff1a;哈​编辑 3.我本来想已近够完美了&#xff0…

Flink学习(七)-单词统计

前言 Flink是流批一体的框架。因此既可以处理以流的方式处理&#xff0c;也可以按批次处理。 一、代码基础格式 //1st 设置执行环境 xxxEnvironment env xxxEnvironment.getEnvironment;//2nd 设置流 DataSource xxxDSenv.xxxx();//3rd 设置转换 Xxx transformation xxxDS.…

Spring Boot中接收各种各样的参数

一、接收json参数&#xff0c;封装为Map 1.1、核心代码 /*** 接收json参数&#xff0c;封装为Map* param servletRequest* return* throws Exception*/ PostMapping("/getParam") public R getParam(HttpServletRequest servletRequest) throws Exception {Map<…

Bootstrap 5 保姆级教程(十二):弹出框 消息弹窗

一、弹出框 1.1 创建弹出框 通过向元素添加 data-bs-toggle"popover" 来来创建弹出框。 title 属性的内容为弹出框的标题&#xff0c;data-bs-content 属性显示了弹出框的文本内容&#xff1a; 注意: 弹出框要写在 JavaScript 的初始化代码里。 以下实例可以在文…

NLP 文本表征方式

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;将文本转换成计算机能够理解和处理的格式是一个基本的步骤。这个过程通常被称为文本表征或文本向量化。下面&#xff0c;我将详细介绍几种常见的文本表征方法&#xff0c;并提供一些例子来说明这些技术是如何应用的。…

投入产出表的分析要点有哪些

投入产出分析是利用投入产出表、投入产出系数和投入产出模型&#xff0c;对国民经济各部门之间的技术经济联系和影响进行分析的一种经济数据分析方法。 一、什么是投入产出表 我国的投入产出表是描述国民经济中各种产品的来源与使用去向的棋盘式平衡表 , 是产品部门 产品部门…

【已解决】电脑设置notepad++默认打开txt

1、以管理员的方式打开notepad 步骤&#xff1a;打开设置 -> 首选项 -> 文件关联 2、 设置Notepad默认打开 按照以下步骤将Notepad设置为默认打开.txt文件&#xff1a; 右键单击任何一个.txt文件。选择“属性”。在“常规”选项卡中&#xff0c;找到“打开方式”&#…

【Interconnection Networks 互连网络】Dragonfly Topology 蜻蜓网络拓扑

蜻蜓拓扑 Dragonfly Topology 1. 拓扑参数2. Topology Description 拓扑描述3. Topology Variations 拓扑变体 蜻蜓拓扑 Dragonfly Topology 1. 拓扑参数 Dragonfly拓扑参数&#xff1a; N N N: 网络中终端(terminal)的总数量 p p p: 连接到每个路由器的终端数量 a a a: 每…

VR全景:为户外游玩体验插上科技翅膀

随着VR全景技术的愈发成熟&#xff0c;无数人感到惊艳&#xff0c;也让各行各业看到了一片光明的发展前景。尤其是越来越多的文旅景区开始引入VR全景技术&#xff0c;相较于以往的静态风景图&#xff0c;显然现在的VR全景结合了动态图像和声音更加吸引人。 VR全景技术正在逐步改…

密码学 | 承诺:Pedersen 承诺 + ZKP

​ &#x1f951;原文&#xff1a;Toward Achieving Anonymous NFT Trading &#x1f951;写在前面&#xff1a;看了篇 22 年 SCI 3 区论文&#xff0c;里面提到在 Pedersen 承诺的揭示阶段可以使用零知识证明&#xff0c;而不必揭示消息明文和随机数。姑且记录一下这个方法。…

Dijkstra算法求最短路

Dijkstra算法可以在图中寻找一个节点&#xff08;称为“源节点”&#xff09;到所有其它节点的最短路径。 文章目录 前言 一、Dijkstra算法是什么&#xff1f; 二、问题介绍 三、朴素版Dijkstra算法 1.图的存储 2.算法实现 四、使用步骤 1.代码如下&#xff08;示例&#xff09…

使用 hiredis 客户端库封装一个简单的 Redis 类

目录 思考一下redis编程的整个过程。 我们作为redis客户端。需要跟redis服务器交互。 封装 Redis 的 C 类的过程可以分为以下几个步骤&#xff1a; 一个完成发布订阅功能的 Redis 类 思考一下redis编程的整个过程。 我们作为redis客户端。需要跟redis服务器交互。 那说白了…

Linux的UDEV机制

udev 机制引入&#xff1a; 手机接入Linux热拔插相关 a. 把手机接入开发板 b. 安装adb工具&#xff0c;在终端输入adb安装指令&#xff1a; sudo apt-get install adb c. dmeg能查看到手机接入的信息&#xff0c;但是输入adb devices会出现提醒 dinsufficient permissions for …

【Java】HashMap、HashTable和ConcurrentHashMap的区别

文章目录 区别一、HashMap1.1基本定义与特性1.2工作原理与实现1.3常用方法1.4性能与优化 二、HashTable三、ConcurrentHashMap3.1基本特点3.2实现原理3.3常用方法3.4适用场景3.5性能优化 HashTable、HashMap和ConcurrentHashMap之间的区别主要体现在线程安全、继承关系与实现接…

Mysql 和 PostgreSQL 到底选啥?

当我深入探讨MySQL和PostgreSQL这两个著名的开源数据库时&#xff0c;我们不仅发现它们在功能、性能和用例方面存在明显的差异&#xff0c;同时也能看出它们各自在特定场景下的独特优势。选择哪一个往往取决于项目的具体需求、团队的熟悉度以及未来的扩展计划。 在这篇文章中&…

kaggle 泰坦尼克号2 得分0.7799

流程 导入所要使用的包引入kaggle的数据集csv文件查看数据集有无空值填充这些空值提取特征分离训练集和测试集调用模型 导入需要的包 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns import warnings warnings.filterwarni…

Vue3: 获取元素DOM的方法

Vue3中获取dom的方法有两种 : ref模板引用和传统方法 1.ref模板引用 模板引用是官方提出的方法&#xff0c;请看下面的例子&#xff1a; <template><canvas ref"solarCanvas" id"solar" width"1300" height"900"></…

K8S 污点和容忍度(Taint,Toleration)

介绍 在 Kubernetes 中&#xff0c;污点&#xff08;Taints&#xff09;和容忍度&#xff08;Tolerations&#xff09;是用于节点调度的一种机制&#xff0c;它们允许你控制哪些 Pod 能够调度到哪些节点上。 污点&#xff08;Taints&#xff09; 污点是节点上的一种属性&…