【观察者设计模式详解】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浏览…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
【单片机期末】单片机系统设计
主要内容:系统状态机,系统时基,系统需求分析,系统构建,系统状态流图 一、题目要求 二、绘制系统状态流图 题目:根据上述描述绘制系统状态流图,注明状态转移条件及方向。 三、利用定时器产生时…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
