【观察者设计模式详解】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浏览…...

Mac flutter环境搭建
一、下载flutter sdk 制作 Android 应用 | Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 1、查看mac电脑处理器选择sdk 2、解压 unzip ~/Downloads/flutter_macos_arm64_3.32.2-stable.zip \ -d ~/development/ 3、添加环境变量 命令行打开配置环境变量文件 ope…...

【51单片机】4. 模块化编程与LCD1602Debug
1. 什么是模块化编程 传统编程会将所有函数放在main.c中,如果使用的模块多,一个文件内会有很多代码,不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数声明,其他.c文…...

麒麟系统使用-进行.NET开发
文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的,如果需要进行.NET开发,则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET,所以要进…...
大数据驱动企业决策智能化的路径与实践
📝个人主页🌹:慌ZHANG-CSDN博客 🌹🌹期待您的关注 🌹🌹 一、引言:数据驱动的企业竞争力重构 在这个瞬息万变的商业时代,“快者胜”的竞争逻辑愈发明显。企业如何在复杂环…...
[QMT量化交易小白入门]-六十二、ETF轮动中简单的评分算法如何获取历史年化收益32.7%
本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读1. 策略概述2. 趋势评分模块3 代码解析4 木头…...

多模态大语言模型arxiv论文略读(110)
CoVLA: Comprehensive Vision-Language-Action Dataset for Autonomous Driving ➡️ 论文标题:CoVLA: Comprehensive Vision-Language-Action Dataset for Autonomous Driving ➡️ 论文作者:Hidehisa Arai, Keita Miwa, Kento Sasaki, Yu Yamaguchi, …...

河北对口计算机高考MySQL笔记(完结版)(2026高考)持续更新~~~~
MySQL 基础概念 数据(Data):文本,数字,图片,视频,音频等多种表现形式,能够被计算机存储和处理。 **数据库(Data Base—简称DB):**存储数据的仓库…...
leetcode 386. 字典序排数 中等
给你一个整数 n ,按字典序返回范围 [1, n] 内所有整数。 你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法。 示例 1: 输入:n 13 输出:[1,10,11,12,13,2,3,4,5,6,7,8,9]示例 2: 输入:n 2…...
stm32—ADC和DAC
ADC和DAC 在嵌入式系统中,微控制器经常需要与现实世界的模拟信号进行交互。STM32微控制器内置了模拟数字转换器(ADC)和数字模拟转换器(DAC),它们是实现这种交互的关键模块。 1. 模拟数字转换器(…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(八)
uboot启动异常及解决 网络问题及解决 打开STM32CubeMX选中ETH1 - A7NS(Linux)Mode:RGMII(Reduced GMII)勾选ETH 125MHz Clock Input修改GPIO引脚如图所示 Net: No ethernet found.生成代码后,修改u-boot下…...