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

news2024/9/19 10:31:24

软件设计模式(四)

  • 观察者模式
    • 一、观察者模式(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();
    }

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public 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;
    }

    @Override
    public 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事件系统到复杂的分布式微服务架构。在选择具体变体时,需要权衡性能、响应速度、解耦程度等多方面因素,以便为系统设计提供最佳的解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Broadcast:Android中实现组件与进程间通信

目录 一&#xff0c;Broadcast和BroadcastReceiver 1&#xff0c;简介 2&#xff0c;广播使用 二&#xff0c;静态注册和动态注册 三&#xff0c;无序广播和有序广播 1&#xff0c;有序广播的使用 2&#xff0c;有序广播的截断 3&#xff0c;有序广播的信息传递 四&am…

力扣(LeetCode)每日一题 1184. 公交站间的距离

题目链接https://leetcode.cn/problems/distance-between-bus-stops/description/?envTypedaily-question&envId2024-09-16 环形公交路线上有 n 个站&#xff0c;按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离&#xff0c;distance[i] 表示编号为 i …

Python燃烧废气排放推断算法模型

&#x1f3af;要点 宏观能耗场景模型参数化输入数据&#xff0c;分析可视化输出结果&#xff0c;使用场景时间序列数据模型及定量和定性指标使用线图和箱线图、饼图、散点图、堆积条形图、桑基图等可视化模型输出结果根据气体排放过程得出其时间序列关系&#xff0c;使用推断模…

nginx基础篇(一)

文章目录 学习链接概图一、Nginx简介1.1 背景介绍名词解释 1.2 常见服务器对比IISTomcatApacheLighttpd其他的服务器 1.3 Nginx的优点(1)速度更快、并发更高(2)配置简单&#xff0c;扩展性强(3)高可靠性(4)热部署(5)成本低、BSD许可证 1.4 Nginx的功能特性及常用功能基本HTTP服…

GlusterFS 分布式文件系统

一、GlusterFS 概述 1.1 什么是GlusterFS GlusterFS 是一个开源的分布式文件系统&#xff0c;它可以将多个存储服务器结合在一起&#xff0c;创建一个大的存储池&#xff0c;供客户端使用。它不需要单独的元数据服务器&#xff0c;这样可以提高系统的性能和可靠性。由于没有…

python毕业设计基于django+vue医院社区医疗挂号预约综合管理系统7918h-pycharm-flask

目录 技术栈和环境说明预期达到的目标具体实现截图系统设计Python技术介绍django框架介绍flask框架介绍解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示操作可行性技术路线感恩大学老师和同学详细视频演示源码获取 技术…

【Finetune】(二)、transformers之Prompt-Tuning微调

文章目录 0、prompt-tuning基本原理1、实战1.1、导包1.2、加载数据1.3、数据预处理1.4、创建模型1.5、Prompt Tuning*1.5.1、配置文件1.5.2、创建模型 1.6、配置训练参数1.7、创建训练器1.8、模型训练1.9、推理&#xff1a;加载预训练好的模型 0、prompt-tuning基本原理 prompt…

【机器学习】任务五:葡萄酒和鸢尾花数据集分类任务

目录 1.实验基础知识 1.1 集成学习 &#xff08;1&#xff09;随机森林 &#xff08;2&#xff09;梯度提升决策树&#xff08;GBDT&#xff09; &#xff08;3&#xff09;XGBoost &#xff08;4&#xff09;LightGBM 1.2 参数优化 &#xff08;1&#xff09;网格搜索…

编写第一个hadoop3.3.6的mapreduce程序

hadoop还是用的上个伪分布环境。 hadoop安装在龙蜥anolis8.9上&#xff0c;开发是在windows下。 1、windows下首先要下载hadoop的包&#xff0c;hadoop-3.3.6.tar.gz&#xff0c;比如我的解压到d:\java\hadoop-3.3.6中。 配置环境&#xff1a;HADOOP_HOME&#xff0c;内容为&am…

ava总结篇系列:Java泛型Java sort用法详解

一. 泛型概念的提出&#xff08;为什么需要泛型&#xff09;&#xff1f; 首先&#xff0c;我们看下下面这段简短的代码: 1 public class GenericTest { 2 3 public static void main(String[] args) { 4 List list new ArrayList(); 5 list.add(&q…

【Elasticsearch系列四】ELK Stack

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

mysql事务的隔离级别学习

事务的隔离级别: ⅰ. 读未提交 ⅱ. 对已提交 &#xff08;解决 脏读&#xff09; ⅲ. 可重复读 &#xff08;解决 不可重复读&#xff09; ⅳ. 串行化 &#xff08;解决 脏读 不可重复读 幻读 问题 &#xff09; 隔离级别分类如下&#xff0c;在不同的隔离级别下可能产生不…

网络安全。

文章目录 目录 文章目录 一. 网络安全概述 二. 密码学原理 三. 报文完整性和数字签名 密码散列函数 报文鉴别码 数字签名 公钥认证 四. HTTPS通信 总结 一. 网络安全概述 网络安全是保护计算机网络及其数据免受各种威胁和攻击的实践和技术。随着互联网的普及和数字化…

Linux shell编程学习笔记81:zcat命令——快速查看压缩文件内容

0 引言 在 Linux shell编程学习笔记80&#xff1a;gzip命令——让文件瘦身-CSDN博客https://blog.csdn.net/Purpleendurer/article/details/141862213?spm1001.2014.3001.5501中&#xff0c;我们使用gzip命令可以创建压缩文件。那么&#xff0c;我们可以使用zcat命令来查看压…

传输层协议——udp/tcp

目录 再谈端口号 udp 协议 理解报头 udp特点 缓冲区 udp使用的注意事项 tcp协议 TCP的可靠性与提高效率的策略 序号/确认序号 窗口大小 ACK&#xff1a; PSH URG RST 保活机制 重传 三次握手(SYN) 四次挥手(FIN) 流量控制 滑动窗口 拥塞控制 延迟应答 捎带应答 面…

JavaScript match() 方法

match() 方法可在字符串内检索指定的值&#xff0c;或找到一个或多个正则表达式的匹配。 如果想了解更多正则表达式教程请查看&#xff1a; RegExp 教程 和我们的 RegExp 对象参考手册。 注意&#xff1a; match() 方法将检索字符串 String Object&#xff0c;以找到一个或多个…

Vue3 项目引入阿里 iconfont 图标和字体的多种方式

&#x1f680; 个人简介&#xff1a;某大型国企资深软件研发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…

计算机人工智能前沿进展-大语言模型方向-2024-09-19

计算机人工智能前沿进展-大语言模型方向-2024-09-19 1. SAM4MLLM: Enhance Multi-Modal Large Language Model for Referring Expression Segmentation Authors: Yi-Chia Chen, Wei-Hua Li, Cheng Sun, Yu-Chiang Frank Wang, Chu-Song Chen SAM4MLLM: 增强多模态大型语言模型…

Java面试篇基础部分-Java线程生命周期

线程的生命周期分别为 新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)这五种状态。   在系统运行过程中有线程不断地被创建,而旧的线程在执行完毕之后被清理,线程通过排队的方式获取共享资源或者锁的时候被阻塞,所以运行中的线程就会在…

基于yolov8的红外小目标无人机飞鸟检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的红外小目标无人机与飞鸟检测系统是一项集成了前沿技术的创新解决方案。该系统利用YOLOv8深度学习模型的强大目标检测能力&#xff0c;结合红外成像技术&#xff0c;实现了对小型无人机和飞鸟等低空飞行目标的快速、准确检测。 YOLOv8作为YOLO系列的…