【观察者设计模式详解】C/Java/JS/Go/Python/TS不同语言实现
简介
观察者模式(Observer Pattern)是一种行为型模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者模式使用三个类Subject、Observer和Client。Subject对象带有绑定观察者到Client对象和从Client对象解绑观察者的方法。我们创建Subject类、Observer抽象类和扩展了抽象类Observer的实体类。
作用
- 一个对象状态更新,其他依赖对象收到通知和自动更新的机制。
- 实现模块化分离,实现主题与观察者交互对象之间的松耦合。
1)观察者定义了对象之间一对多的关系。
2)被观察者(主题)用一个共同的接口来更新观察者。
3)观察者和被观察者用松耦合方式结合,被观察者不知道观察者的细节,只知道观察者实现了观察者接口。
实现步骤
- 创建观察者observer基础接口,包含主题和更新方法
- 创建主题subject抽象类,包含observer列表以及添加和删除方法
- 创建具体的主题类,实现通知方法,发布通知时轮询通知全部观察者
- 创建多个具体观察者,与主题关联,并实现自己的更新方法
- 客户调用时先声明主题,再将观察者分别添加到主题,当主题发布通知时,观察者自动更新
UML
Java代码
观察者接口
// ObserverAPI.java 观察者接口,Java 9已经默认支持Observer接口 | |
// 这里避免冲突采取ObserverAPI命名 | |
public interface ObserverAPI { | |
public Subject subject = null; | |
public void update(String content); | |
} |
具体观察者
// ConcreteObserver.java 具体的观察者实现类,也可以看成订阅者,关联对应的主题类。 | |
// 不同的观察者也可以对应多个主题 | |
public class ConcreteObserver implements ObserverAPI { | |
public Subject subject; | |
// 给观察者绑定主题,同时把观察者添加到主题列表 | |
public ConcreteObserver(Subject subject) { | |
this.subject = subject; | |
this.subject.register((ObserverAPI) this); | |
} | |
// 观察者发出更新通知,不用单独告诉订阅者,由订阅者自行监听 | |
public void update(String content) { | |
System.out.println(String.format("%s::update() [subject.name = %s content = %s]", | |
this.getClass().getName(), | |
this.subject.getName(), content)); | |
} | |
} |
// ConcreteObserver2.java 具体的观察者实现类,也可以看成订阅者,关联对应的主题类。 | |
// 不同的观察者可以对应不同的主题。 | |
public class ConcreteObserver2 implements ObserverAPI { | |
// 这里没有在构造器就绑定某个主题,而是从客户角度去注册观察者 | |
public ConcreteObserver2() { | |
} | |
// 观察者发出更新通知,观察者自行监听 | |
public void update(String content) { | |
System.out.println(String.format("%s::update() [content = %s]", | |
this.getClass().getName(), content)); | |
} | |
} |
抽象主题类
// Subject.java 定义抽象主题类或者接口,供具体主题类继承 | |
public abstract class Subject { | |
private String name; | |
// protected Set<ObserverAPI> observers = new HashSet<>(); | |
protected List<ObserverAPI> observers = new ArrayList<>(); | |
public String getName() { | |
return name; | |
} | |
public void setName(String name) { | |
this.name = name; | |
} | |
public void register(ObserverAPI observer) { | |
System.out.println(this.getClass().getName() + "::register() [observer = " + observer.getClass().getSimpleName() + "]"); | |
observers.add(observer); | |
} | |
public void remove(ObserverAPI observer) { | |
observers.remove(observer); | |
} | |
// 通知由具体类来实现逻辑 | |
public abstract void notify(String name); | |
} |
具体主题类
// ConcreteSubject.java 观察者主题类,也是发布者,重写具体的通知方法。不同主题可以关联不同的观察者。 | |
public class ConcreteSubject extends Subject { | |
public ConcreteSubject(String name) { | |
this.setName(name); | |
} | |
// 不同的主题类有自己的通知方法,批量通知绑定的观察者 | |
@Override | |
public void notify(String content) { | |
System.out.println(this.getClass().getName() + "::notify() [content = " + content + "]"); | |
for (Object observer : this.observers) { | |
((ObserverAPI) observer).update(content); | |
} | |
} | |
} |
测试调用
/** | |
* 观察者模式应用非常广泛,主要是观察者提前绑定到发布者 | |
* 当发布者发布消息时,批量广播通知,而无需逐一通知 | |
* 观察者监听到消息后自己决定采取哪一种行为 | |
*/ | |
// 定义一个主题,也就是发布者 | |
Subject concreteSubject = new ConcreteSubject("subject1"); | |
// 再声明观察者,通过构造器注册到主题上 | |
ObserverAPI observer1 = new ConcreteObserver(concreteSubject); | |
// 也可以单独给主题注册一个新的观察者 | |
concreteSubject.register(new ConcreteObserver2()); | |
// 可以移除观察者对象,可以打开注释试下 | |
// concreteSubject.remove(observer1); | |
// 主题开始发布新通知,各观察者自动更新 | |
concreteSubject.notify("hello, this is broadcast."); | |
Python代码
观察者接口
# ObserverAPI.py 观察者抽象父类,定义一些公共方法 | |
class ObserverAPI: | |
def __init__(self, name): | |
self.name = name | |
# 观察者发出更新通知,观察者自行监听 | |
def update(self, content): | |
print(self.__class__.__name__ + '::update() [content = ' + content + ']') | |
def set_name(self, name): | |
self.name = name |
具体观察者
# ConcreteObserver.py 具体的观察者实现类,也可以看成订阅者,关联对应的主题类。 | |
# 不同的观察者也可以对应多个主题 | |
from src.ObserverAPI import ObserverAPI | |
# 具体的观察者实现类,也可以看成订阅者,关联对应的主题类。 | |
# 不同的观察者也可以对应多个主题 | |
class ConcreteObserver(ObserverAPI): | |
# 给观察者绑定主题,同时把观察者添加到主题列表 | |
def __init__(self, subject, name): | |
ObserverAPI.__init__(self, name) | |
# python3支持的父类调用 | |
# super(ConcreteObserver, self).__init__(name) | |
# super().__init__(name) | |
self.subject = subject | |
subject.register(self) | |
# 观察者发出更新通知,不用单独告诉订阅者,由订阅者自行监听 | |
def update(self, content): | |
print(self.__class__.__name__ + '::update() [subject.name = ' + | |
self.subject.name + ' content = ' + content + ']') |
# ConcreteObserver2.py 具体的观察者实现类,也可以看成订阅者,关联对应的主题类。 | |
# 不同的观察者可以对应不同的主题。 | |
from src.ObserverAPI import ObserverAPI | |
# 具体的观察者实现类,也可以看成订阅者,关联对应的主题类。 | |
# 不同的观察者可以对应不同的主题。 | |
class ConcreteObserver2(ObserverAPI): | |
# 这里没有在构造器就绑定某个主题,而是从客户角度去注册观察者 | |
# 观察者发出更新通知,观察者自行监听 | |
# def update(self, content): | |
# print(self.__class__.__name__ + '::update() [content = ' + content +']') | |
pass |
抽象主题类
# Subject.py 定义抽象主题类或者接口,供具体主题类继承 | |
class Subject: | |
def __init__(self, name): | |
self.name = name | |
self.observers = [] | |
def get_name(self): | |
return self.name | |
def set_name(self, name): | |
self.name = name | |
def register(self, observer): | |
print(self.__class__.__name__ + '::register() [observer = ' + | |
observer.__class__.__name__ + ']') | |
self.observers.append(observer) | |
def remove(self, observer): | |
self.observers.remove(observer) | |
# 通知由具体类来实现逻辑 | |
def notify(self, name): | |
pass |
具体主题类
// ConcreteSubject.py 观察者主题类,也是发布者,重写具体的通知方法。不同主题可以关联不同的观察者。 | |
from src.Subject import Subject | |
# 观察者主题类,也是发布者,重写具体的通知方法。不同主题可以关联不同的观察者。 | |
class ConcreteSubject(Subject): | |
# 不同的主题类有自己的通知方法,批量通知绑定的观察者 | |
def notify(self, content): | |
print(self.__class__.__name__ + '::notify() [content = ' + content + | |
']') | |
for observer in self.observers: | |
observer.update(content) |
测试调用
import sys | |
import os | |
os_path = os.getcwd() | |
sys.path.append(os_path) | |
from src.ConcreteSubject import ConcreteSubject | |
from src.ConcreteObserver import ConcreteObserver | |
from src.ConcreteObserver2 import ConcreteObserver2 | |
def test(): | |
''' | |
* 观察者模式应用非常广泛,主要是观察者提前绑定到发布者 | |
* 当发布者发布消息时,批量广播通知,而无需逐一通知 | |
* 观察者监听到消息后自己决定采取哪一种行为 | |
''' | |
# 定义一个主题,也就是发布者 | |
concrete_subject = ConcreteSubject('subject1') | |
# 再声明观察者,通过构造器注册到主题上 | |
observer1 = ConcreteObserver(concrete_subject, 'observer1') | |
# 也可以单独给主题注册一个新的观察者 | |
observer2 = ConcreteObserver2('observer2') | |
concrete_subject.register(observer2) | |
# 可以移除观察者对象 | |
# concrete_subject.remove(observer1) | |
# 主题开始发布新通知,各观察者自动更新 | |
concrete_subject.notify('hello, this is broadcast.') | |
if __name__ == '__main__': | |
print(__file__) | |
print("test start:") | |
test() | |
更多语言版本
不同语言实现设计模式:https://github.com/microwind/design-pattern
相关文章:

【观察者设计模式详解】C/Java/JS/Go/Python/TS不同语言实现
简介 观察者模式(Observer Pattern)是一种行为型模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 观察者模式使用三个类Subject、Observer和Client。Subject…...
精细解析中文公司名称:智能分词工具助力地名、品牌名、行业词和后缀提取
精细解析中文公司名称:智能分词工具助力地名、品牌名、行业词和后缀提取 中文公司名称分词工具,支持公司名称中的地名,品牌名(主词),行业词,公司名后缀提取。 对公司名文本解析,识…...

网络编程(JavaEE初阶系列10)
目录 前言: 1.网络编程的基础 1.1为什么需要网络编程 1.2什么是网络编程 1.3网络编程中的基本概念 1.3.1发送端和接收端 1.3.2请求和响应 1.3.3客户端和服务端 2.Socket套接字 2.1概念 2.2分类 3.UDP数据报套接字编程 3.1DataGramSocket API 3.2Datagr…...
Git常用的指令
Git常用的指令 OMMP提交代码的流程 0、配置: git config --list 查看当前配置 git congig --global user.name user 这个会显示你的提交到git的名字 格式:git config [–local|–global|–system] –unset section.key 格式:git config [–l…...

LoadRunner(2)
一、Controller 1.1场景设计 1.通过VUG打开 施压机器:发起请求的角色(用户本地电脑) 被压机器:处理请求的角色(服务器) 2.直接双击Controller 场景设计:需要关注三个部分 第一部分: 第二部分: 2.1运行场景…...

CTF之逆向之阿里巴巴
题目地址:http://www.shiyanbar.com/ctf/13 题目预览: 解题过程: 1、下载附件发现是exe文件 2、使用PEid和Detect It Easy查壳 和 开发语言,发现没有加壳,都是用C#开发的 3、C#和Java Python属于解释型语言ÿ…...

Labview控制APx(Audio Precision)进行测试测量(五)
驱动程序 VIs如何处理配置设置中的单元 APx500 应用程序具有复杂的控件,具有以下功能: 数值和单位组合在一个控制中(例如,1.000 Vrms ) •值转换为 SI 格式(例如,1.000 mVrms 或 1.000 μVrms) •单位之间的转换发生在控制(例如,V…...

在单元测试中使用Jest模拟VS Code extension API
对VS Code extension进行单元测试时通常会遇到一个问题,代码中所使用的VS Code编辑器的功能都依赖于vscode库,但是我们在单元测试中并没有添加对vscode库的依赖,所以导致运行单元测试时出错。由于vscode库是作为第三方依赖被引入到我们的VS C…...

django boostrap html实现可拖拽的左右布局,鼠标拖动调整左右布局的大小或占比
一、实现的效果 最近需要在Django项目中,实现一个左右布局的html页面,页面框架使用的是boostrap。但这个布局不是简单的左右分栏布局,而是需要实现可以通过鼠标拖拽的方式动态调整左右两侧布局的大小和占比。效果大致如下: 一开始,页面分为左右两块布局: 鼠标放到中间的…...

谈谈闭包和闭包使用场景
一、什么是闭包 概念:闭包还是作用域的一种特殊应用 二、触发闭包的情况 1.函数当做返回值被返回 2.函数当做参数被传递 3.自执行匿名函数 //情况1:函数当做返回值被返回 function fn(){const a 1;return function(){console.log(a) //1}; } const a …...
MATLAB算法实战应用案例精讲-【图像处理】边界框锚框
目录 目标检测 应用场景 目标检测发展历程 常用数据集 边界框(bounding box)...
04什么场景要用到微服务
一句话导读 根据微服务的特点,可以总结为在构建复杂的、大型的、分布式的、高可用、高并发、高性能的应用时可以使用微服务架构。 目录 一句话导读 一、微服务适用场景 1.业务复杂,模块多且相对独立 2.团队多,管理隔离 3.应用规模大&#…...

.NET SqlSuger 简单介绍,超快开发数据库
文章目录 前言SqlSugar使用我的环境Nuget 安装新建连接串DB First 和 Code First使用增删改查 总结 前言 我之前介绍过EFCore 怎么使用Nuget快速创建数据库,我之后发现SqlSugar更快。这里简单再说一下SqlSugar如何使用 .NET Core 数据库DB First自动生成࿰…...
SpringBoot复习:(28)【前后端不分离】自定义View
一、自定义View package cn.edu.tju.view;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Comp…...

springcloud3 springcloud stream的学习以及案例(了解)
一 springcloud stream的作用 1.1 springcloud stream作用 stream屏蔽底层消息中间件的差异,降低切换成本,统一消息的编程模型。 stream中的消息通信模式遵循了“发布-订阅”模式。 1.2 Binder作用 通过定义绑定器Binder作为中间层,实现…...
Kotlin理解内置函数
目录 一 内置函数1.1 apply 函数1.2 let 函数1.3 run函数1.4 with函数1.5 also函数1.6 takeIf函数1.7 takeUnless函数1.8 总结 Kotlin内置函数包括:let、run、with、apply、also,这些函数都是在Any类中定义的扩展函数,所以任何对象都可以调用…...

手机app测试
一、安装、卸载、更新、运行 1.安装、卸载 应用是否可以正常安装(命令行安装;apk/ipa安装包安装)(有网,无网是否都正常)卸载过程中出现死机,断电,重启等意外的情况&…...
Centos部署Git
Centos部署Git 文章目录 Centos部署Git部署步骤初始化配置免登录 部署步骤 初始化 -- 安装git yum install git配置免登录 配置git下载代码时 每次都需要输入密码的事情 -- 生成 gitconfig 文件 git config --global credential.helper store -- 配置登录邮箱 git config …...
k8s 控制器
Kubernetes(K8S)是一种开源的容器编排平台,它可以自动化地管理容器化应用程序的部署、扩展和运行。K8S中的控制器是一种重要的组件,它可以确保应用程序的状态与期望的状态一致。在K8S中,有五种常见的控制器,…...

谷歌关闭跨域限制.(生成一个开发浏览器),Chrome关闭跨域
(一)、首先找到浏览器在电脑磁盘中的位置,并复制 (二)、复制一个浏览器的快捷方式到桌面(不影响正常浏览器) (三)、chrom鼠标右键属性,修改快捷方式的目标 (四)chrome.exe 后面添加 --disable-web-security --user-data-dir 复制的Chrome浏览…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...