【观察者设计模式详解】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浏览…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
