当前位置: 首页 > news >正文

【软件设计】常用设计模式--观察者模式

软件设计模式(四)

  • 观察者模式
    • 一、观察者模式(Observer Pattern)
      • 1. 概念
      • 2. 模式结构
      • 3. UML 类图
      • 4. 实现方式
        • C# 示例
          • 步骤1:定义观察者接口
          • 步骤2:定义主题接口
          • 步骤3:实现具体主题
          • 步骤4:实现具体观察者
          • 步骤5:使用观察者模式
        • Java 示例
          • 步骤1:定义观察者接口
          • 步骤2:定义主题接口
          • 步骤3:实现具体主题
          • 步骤4:实现具体观察者
          • 步骤5:使用观察者模式
      • 5. 优点
      • 6. 缺点
      • 7. 应用场景
    • 二、观察者模式的变体与深入应用
      • 1. 观察者模式的变体
        • 变体1: 推与拉模式(Push vs. Pull Model)
        • 变体2: 同步与异步通知
        • 变体3: 链式观察者模式
        • 变体4: 优先级观察者
      • 2. 实际应用场景
        • 场景1: GUI 事件系统中的事件分发
        • 场景2: 数据流处理与实时监控
        • 场景3: 发布-订阅系统
        • 场景4: MVC(Model-View-Controller)模式
        • 场景5: 微服务架构中的事件驱动
      • 3. 观察者模式的实际挑战
        • 问题1: 过度依赖
        • 问题2: 循环依赖
        • 问题3: 内存泄漏
    • 总结

观察者模式

一、观察者模式(Observer Pattern)

1. 概念

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。观察者模式常用于实现分布式事件处理系统,特别是在GUI框架和事件驱动的系统中。

通过观察者模式,多个对象可以观察同一个主题(Subject),当主题状态变化时,所有观察者(Observer)都会收到通知并做出相应的响应。

2. 模式结构

观察者模式包含以下几个关键角色:

  • 主题(Subject):持有观察者的引用,并负责管理观察者(添加、移除、通知等)。

  • 观察者(Observer):定义一个更新接口,当主题状态变化时更新自己。

  • 具体主题(Concrete Subject):实现主题的通知逻辑,维护内部状态并在状态变化时通知观察者。

  • 具体观察者(Concrete Observer):实现观察者的更新接口,根据主题通知的变化做出响应。

3. UML 类图

Subject
+Attach()
+Detach()
+Notify()
Observer
+Update()
ConcreteObserver
+Update()

4. 实现方式

C# 示例
步骤1:定义观察者接口
public interface IObserver
{void Update(string message);
}
步骤2:定义主题接口
public interface ISubject
{void Attach(IObserver observer);void Detach(IObserver observer);void Notify();
}
步骤3:实现具体主题
public class ConcreteSubject : ISubject
{private List<IObserver> _observers = new List<IObserver>();private string _subjectState;public string State{get { return _subjectState; }set{_subjectState = value;Notify();}}public void Attach(IObserver observer){_observers.Add(observer);}public void Detach(IObserver observer){_observers.Remove(observer);}public void Notify(){foreach (var observer in _observers){observer.Update(_subjectState);}}
}
步骤4:实现具体观察者
public class ConcreteObserver : IObserver
{private string _name;public ConcreteObserver(string name){_name = name;}public void Update(string message){Console.WriteLine($"{_name} received message: {message}");}
}
步骤5:使用观察者模式
class Program
{static void Main(string[] args){// 创建主题ConcreteSubject subject = new ConcreteSubject();// 创建观察者IObserver observer1 = new ConcreteObserver("Observer 1");IObserver observer2 = new ConcreteObserver("Observer 2");// 注册观察者subject.Attach(observer1);subject.Attach(observer2);// 改变主题状态并通知观察者subject.State = "State changed!";// Output: // Observer 1 received message: State changed!// Observer 2 received message: State changed!}
}
Java 示例
步骤1:定义观察者接口
public interface Observer {void update(String message);
}
步骤2:定义主题接口
public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}
步骤3:实现具体主题
import java.util.ArrayList;
import java.util.List;public class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<>();private String state;public void setState(String state) {this.state = state;notifyObservers();}@Overridepublic void attach(Observer observer) {observers.add(observer);}@Overridepublic void detach(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(state);}}
}
步骤4:实现具体观察者
public class ConcreteObserver implements Observer {private String name;public ConcreteObserver(String name) {this.name = name;}@Overridepublic void update(String message) {System.out.println(name + " received message: " + message);}
}
步骤5:使用观察者模式
public class Main {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();Observer observer1 = new ConcreteObserver("Observer 1");Observer observer2 = new ConcreteObserver("Observer 2");subject.attach(observer1);subject.attach(observer2);subject.setState("New State");// Output:// Observer 1 received message: New State// Observer 2 received message: New State}
}

5. 优点

  • 松耦合: 观察者与主题之间的耦合度较低,主题不需要了解观察者的具体实现。
  • 动态观察者管理: 可以随时添加或移除观察者,不影响系统的其他部分。
  • 事件通知机制: 广泛应用于事件驱动的系统中,实现异步通知机制。

6. 缺点

  • 通知顺序问题: 多个观察者的通知顺序可能无法确定,导致结果的不可预测性。
  • 性能开销: 如果观察者数量过多,通知操作可能带来性能开销。
  • 内存泄漏风险: 如果没有正确地解除观察者注册,可能会造成内存泄漏。

7. 应用场景

  • 场景1: GUI事件系统
    观察者模式广泛用于GUI应用程序中,按钮的点击、文本框的输入等事件都可以通过观察者模式通知相关对象。
  • 场景2: 订阅-发布系统
    在消息系统中,发布者可以是主题,订阅者是观察者。当发布者有新消息时,订阅者会收到通知。
  • 场景3: 数据绑定
    在现代前端框架(如React、Vue等)中,观察者模式用于实现数据的双向绑定,当数据变化时自动更新视图。

二、观察者模式的变体与深入应用

1. 观察者模式的变体

观察者模式虽然简单,但可以通过不同的方式进行扩展和变体以适应更复杂的场景。以下是几种常见的变体:

变体1: 推与拉模式(Push vs. Pull Model)

在经典的观察者模式中,通常采用推(Push)模式,即主题在状态改变时主动将更新数据推送给所有观察者。而在拉(Pull)模式中,主题只通知观察者有变化,但具体数据由观察者自行从主题中拉取。

  • 推模式:主题向观察者提供足够的更新数据,观察者直接获取并处理。适合更新数据明确的场景。
  • 拉模式:主题只提供最小的信息,观察者决定是否拉取完整数据,通常用于复杂的更新场景,以减少不必要的传输。
// 拉模式下,Observer在收到通知后,主动获取数据
public void Update(ConcreteSubject subject)
{var state = subject.GetState();Console.WriteLine($"Observer fetched new state: {state}");
}
变体2: 同步与异步通知

经典的观察者模式通常是同步通知,即主题在通知观察者时,所有观察者都同步更新。但在某些高负载或分布式系统中,可以采用异步通知,即主题在状态改变时,异步通知观察者,允许观察者在不同的时间点处理更新。

  • 同步通知:适合简单或小规模系统,能确保观察者在通知后立即获取最新状态。
  • 异步通知:适合需要并发处理或系统延迟的场景,如消息队列、分布式系统。
变体3: 链式观察者模式

在链式观察者模式中,观察者之间形成链式依赖,一个观察者更新后会通知下一个观察者,依次传递下去。适合于需要逐步更新的场景,如责任链模式与观察者模式结合使用。

public class ChainedObserver : IObserver
{private IObserver _nextObserver;public ChainedObserver(IObserver nextObserver){_nextObserver = nextObserver;}public void Update(string message){Console.WriteLine($"Processing in current observer: {message}");_nextObserver?.Update(message);}
}
变体4: 优先级观察者

在某些系统中,可能需要不同观察者根据优先级来接收通知。优先级观察者模式允许为每个观察者分配一个优先级,主题按照优先级的顺序通知观察者。这样可以确保关键观察者优先响应,减少延迟。

// 基于优先级的通知系统
public class PriorityObserver : IObserver, IComparable<PriorityObserver>
{public int Priority { get; }public PriorityObserver(int priority){Priority = priority;}public void Update(string message){Console.WriteLine($"Priority {Priority} observer received: {message}");}public int CompareTo(PriorityObserver other){return Priority.CompareTo(other.Priority);}
}

2. 实际应用场景

场景1: GUI 事件系统中的事件分发

现代GUI应用程序(如WPF、Swing、WinForms等)经常使用观察者模式处理用户界面上的事件。例如,当用户点击按钮时,按钮是主题,它的点击事件会触发多个观察者(如回调函数、事件处理程序),以执行相关逻辑。

  • 推模式:可以将用户操作的详细信息(如点击坐标、鼠标状态)推送给观察者。
  • 拉模式:观察者只收到“按钮被点击”这一基本通知,然后自行获取相关的事件数据。
场景2: 数据流处理与实时监控

在金融市场、物联网、实时监控等场景中,观察者模式可以用于数据流的处理与监控。例如,股票价格实时变动时,所有观察者(投资分析工具、交易平台等)都会被通知。

  • 异步通知:由于数据量大,可以通过消息队列实现异步更新,观察者可以根据需要处理数据流。
  • 优先级通知:关键监控设备或算法可能需要优先处理,以确保及时响应市场变化。
场景3: 发布-订阅系统

观察者模式在发布-订阅系统中广泛使用。发布者(主题)发布消息时,所有订阅者(观察者)会接收消息并更新。这种系统应用于多种场景,如新闻订阅、RSS订阅、电子邮件通知等。

  • 拉模式:订阅者可以根据消息头信息判断是否需要拉取完整内容,适合减少带宽占用的场景。
  • 异步处理:特别适合分布式系统,订阅者可以异步接收消息并处理。
场景4: MVC(Model-View-Controller)模式

MVC是软件开发中的一种常见架构模式,观察者模式在其中扮演了重要角色。模型(Model)作为主题,持有应用程序的数据,当数据改变时通知视图(View),视图可以通过观察者模式动态更新界面。控制器(Controller)作为观察者与模型交互,推动状态的变化。

// MVC中的简单应用
public class Model : ISubject
{private List<IObserver> _observers = new List<IObserver>();private string _data;public string Data{get => _data;set{_data = value;Notify();}}public void Attach(IObserver observer) => _observers.Add(observer);public void Detach(IObserver observer) => _observers.Remove(observer);public void Notify(){foreach (var observer in _observers)observer.Update(_data);}
}public class View : IObserver
{public void Update(string message){Console.WriteLine($"View updated with new data: {message}");}
}
场景5: 微服务架构中的事件驱动

在微服务架构中,不同的服务之间通过事件进行通信。每个服务可以作为一个观察者,当某个服务发布事件时,所有依赖的服务都会收到通知并做出相应处理。这种架构通过事件驱动模式实现高解耦和灵活扩展。

  • 异步通知:通过消息队列(如RabbitMQ、Kafka)实现异步事件驱动,确保服务的独立性与高效通信。
  • 优先级处理:某些关键服务可以优先获取事件,并立即作出响应。

3. 观察者模式的实际挑战

问题1: 过度依赖

观察者模式虽然在解耦上提供了灵活性,但在某些复杂场景下,系统可能会过度依赖观察者,导致难以追踪更新的来源和原因。

问题2: 循环依赖

如果多个观察者之间相互依赖,可能会引发循环通知的问题,导致系统陷入死循环或过多的无效更新。

问题3: 内存泄漏

如果主题没有正确地管理观察者(如没有移除已经不需要的观察者),可能会导致内存泄漏,特别是在长生命周期的系统中。


总结

观察者模式的变体为不同的应用场景提供了灵活性,从推拉模型到优先级处理和异步通知,它适用于从简单的GUI事件系统到复杂的分布式微服务架构。在选择具体变体时,需要权衡性能、响应速度、解耦程度等多方面因素,以便为系统设计提供最佳的解决方案。

相关文章:

【软件设计】常用设计模式--观察者模式

软件设计模式&#xff08;四&#xff09; 观察者模式一、观察者模式&#xff08;Observer Pattern&#xff09;1. 概念2. 模式结构3. UML 类图4. 实现方式C# 示例步骤1&#xff1a;定义观察者接口步骤2&#xff1a;定义主题接口步骤3&#xff1a;实现具体主题步骤4&#xff1a;…...

东北非国企就职体验

有感而发&#xff0c;校招毕业选了个非央国企但偏稳的工作&#xff0c;属于事儿少离家近钱还可以。不忙&#xff0c;收入在东北也还不错&#xff0c;可是看到近期那些考上公务员那些有编制的pyq&#xff0c;真的是很感叹他们的生活真的是丰富多彩。 虽然我不忙&#xff0c;但是…...

经典sql题(二)求连续登录最多天数用户

示例数据 假设我们的 test 表有以下数据&#xff1a; iddate12023-10-01 08:00:0012023-10-02 09:00:0012023-10-03 10:00:0012023-10-05 11:00:0022023-10-01 10:00:0022023-10-02 12:00:0022023-10-03 14:00:0022023-10-04 15:00:0032023-10-01 16:00:0032023-10-02 16:00:…...

A. Closest Point

time limit per test 2 seconds memory limit per test 512 megabytes Consider a set of points on a line. The distance between two points ii and jj is |i−j||i−j|. The point ii from the set is the closest to the point jj from the set, if there is no othe…...

沟通更高效:微信群转移至企业微信操作攻略!

微信群转移到企业微信并不难&#xff0c;具体操作如下&#xff1a; 打开移动端企业微信主页&#xff0c;找到微信聊天栏中的【接收微信中的工作消息】&#xff1b; 点击【前往微信选择群聊】&#xff0c; 跳转到微信&#xff1b; 选择微信上的工作群聊&#xff0c;只能选择作…...

计算机毕业设计 基于Python Django的旅游景点数据分析与推荐系统 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…...

关于安卓App自动化测试的一些想法

安卓App自动化一般使用PythonAppium。页面元素通常是使用AndroidStudio中的UI Automator Viewer工具来进行页面元素的追踪。但是这里涉及到一个问题就是&#xff0c;安卓apk在每次打包的时候&#xff0c;会进行页面的混淆以及加固&#xff0c;所以导致每次apk打包之后会出现页面…...

Bigemap GIS Office 2024注册机 全能版地图下载软件

对于需要利用GIS信息进行编辑、设计的用户来说&#xff0c;Bigemap GIS Office占有重要地位。用户可以使用Bigemap GIS Office作为工具进行设计、分析、共享、管理和发布地理信息。Bigemap GIS Office能实现多种数据流转、嵌入、融合以及更多地为用户提供数据的增强处理及多种分…...

秦时明月6.2魔改版+GM工具+虚拟机一键端

今天给大家带来一款单机游戏的架设&#xff1a;秦时明月。 另外&#xff1a;本人承接各种游戏架设&#xff08;单机联网&#xff09; 本人为了学习和研究软件内含的设计思想和原理&#xff0c;带了架设教程仅供娱乐。 教程是本人亲自搭建成功的&#xff0c;绝对是完整可运行…...

firewalld实现NAT端口转发

1、准备工作 # 开启 NAT 转发 firewall-cmd --permanent --zonepublic --add-masquerade # 开放 DNS 使用的 80 端口&#xff0c;tcp# 必须&#xff0c;否则其它机器无法进行域名解析 firewall-cmd --zonepublic --add-port80/tcp --permanent # 检查是否允许 NAT 转发 f…...

中国电子学会202309青少年软件编程(Python)等级考试试卷(二级)真题

青少年软件编程(Python)等级考试试卷(二级) 分数:100 题数:37 一、单选题(共25题,每题2分,共50分) 1、 yyh = [2023, 杭州亚运会, [拱宸桥, 玉琮莲叶]] jxw = yyh[2][0] print(jxw[1] * 2)以上代码运行结果是?( ) A. 宸宸 B. 杭杭 C. 玉玉 D. 州州 2、阿宝…...

第四天旅游线路预览——从贾登峪到喀纳斯景区入口(贾登峪游客服务中心)

第四天&#xff1a;从贾登峪到喀纳斯风景区入口&#xff0c;晚上住宿贾登峪&#xff1b; 从贾登峪到喀纳斯景区入口&#xff08;贾登峪游客服务中心&#xff09;&#xff1a; 搭乘贾登峪①路车&#xff0c;路过三湾到达景区换乘中心&#xff0c;路程时长约40分钟&#xff1b; …...

个人常用命令

文章目录 linux命令基本命令screen docker命令 linux命令 基本命令 查看文件大小&#xff1a;sudo du -sh /文件路径 查看当前目录下所有文件夹大小&#xff0c;不进行递归&#xff1a;sudo du -h --max-depth1 远程复制文件:rsync -avz -e ssh -p 端口号 ip地址:/远程文件地…...

如何根据协议请求去捕捉在个文件中发出去的

场景&#xff1a;随着业务越来越复杂&#xff0c;一个“触发”可能发出去N个协议&#xff0c;此时有某一个协议发生了报错&#xff0c;需要去找这个协议&#xff0c;去文件中走读逻辑&#xff0c;去找该协议&#xff0c;效率很慢&#xff0c;业务极其复杂的情况下&#xff0c;很…...

Lombok -----此java库 常用的注解及其功能总结

总结 Lombok 是一个 Java 库&#xff0c;它可以帮助开发者减少在 Java 中编写那些繁琐的“boilerplate”代码的工作量&#xff0c;比如 getter 方法、setter 方法、构造函数、toString 方法等。 通过简单的注解&#xff0c;Lombok 能够自动为你的类生成这些方法&#xf…...

纯前端表格导出Excel

先写好两个js文件 直接复制粘贴 文件目录是这样的 Bolb.js /* eslint-disable */ /* Blob.js* A Blob implementation.* 2014-05-27** By Eli Grey, http://eligrey.com* By Devin Samarin, https://github.com/eboyjr* License: X11/MIT* See LICENSE.md*//*global self, …...

sourceTree保姆级教程7:(合并某次提交)

在日常开发过程中&#xff0c;大家有时候并非都是在同一个分支进行开发&#xff0c;可能存在多人的情况下开发。根据上线的需求依次提交代码。当然也可能存在交叉提交的情况。此时应如何在master分支去合并具体某一次的提交呢&#xff1f;下面就开始了&#xff1a; 1.打开本地…...

JVM面试知识点手册

第一部分&#xff1a;JVM 概述 1.1 JVM 简介 Java Virtual Machine&#xff08;JVM&#xff09; 是 Java 语言的核心组件&#xff0c;负责将 Java 程序编译后的字节码&#xff08;bytecode&#xff09;转换为机器指令&#xff0c;并在目标机器上执行。JVM 提供了硬件和操作系…...

Vue3.0组合式API:使用reactive()、ref()创建响应式代理对象

Vue3.0组合式API系列文章&#xff1a; 《Vue3.0组合式API&#xff1a;setup()函数》 《Vue3.0组合式API&#xff1a;使用reactive()、ref()创建响应式代理对象》 《Vue3.0组合式API&#xff1a;computed计算属性、watch监听器、watchEffect高级监听器》 《Vue3.0组合式API&…...

kubernetes调度2

1、各种缩写的应用 [rootk8s-master test]# kubectl get rsNAME DESIRED CURRENT READY AGEtest001-64c7957b5c 2 2 2 8m59stest001-698b98bb8f 0 0 0 12m[rootk8s-master test]# kubectl get replicas…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

css的定位(position)详解:相对定位 绝对定位 固定定位

在 CSS 中&#xff0c;元素的定位通过 position 属性控制&#xff0c;共有 5 种定位模式&#xff1a;static&#xff08;静态定位&#xff09;、relative&#xff08;相对定位&#xff09;、absolute&#xff08;绝对定位&#xff09;、fixed&#xff08;固定定位&#xff09;和…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中&#xff0c;API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关&#xff0c;Kong凭借其插件化架构…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!

今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等&#xff0c;设置经线、纬线都以10间隔显示。 2、需要插入背会归线&#xf…...

深度解析云存储:概念、架构与应用实践

在数据爆炸式增长的时代&#xff0c;传统本地存储因容量限制、管理复杂等问题&#xff0c;已难以满足企业和个人的需求。云存储凭借灵活扩展、便捷访问等特性&#xff0c;成为数据存储领域的主流解决方案。从个人照片备份到企业核心数据管理&#xff0c;云存储正重塑数据存储与…...

【R语言编程——数据调用】

这里写自定义目录标题 可用库及数据集外部数据导入方法查看数据集信息 在R语言中&#xff0c;有多个库支持调用内置数据集或外部数据&#xff0c;包括studentdata等教学或示例数据集。以下是常见的库和方法&#xff1a; 可用库及数据集 openintro库 该库包含多个教学数据集&a…...

盲盒一番赏小程序:引领盲盒新潮流

在盲盒市场日益火爆的今天&#xff0c;如何才能在众多盲盒产品中脱颖而出&#xff1f;盲盒一番赏小程序给出了答案&#xff0c;它以创新的玩法和优质的服务&#xff0c;引领着盲盒新潮流。 一番赏小程序的最大特色在于其独特的赏品分级制度。赏品分为多个等级&#xff0c;从普…...