在软件开发中,我们经常遇到这样的场景:一个对象的状态变化需要通知其他多个对象,并且这些对象需要根据变化做出相应的反应。比如,用户界面中的数据变化需要实时反映到多个图表上,或者电商系统中的库存变化需要通知订单系统和推荐系统。观察者模式(Observer Pattern)正是为解决这类问题而生的经典设计模式。

一、观察者模式概述

观察者模式是一种行为型设计模式,它定义了对象之间的一种一对多的依赖关系,当一个对象(称为"主题"或"被观察者")的状态发生改变时,所有依赖于它的对象(称为"观察者")都会得到通知并自动更新。

1.1 模式结构

观察者模式包含四个核心角色:

  1. Subject(主题/被观察者)

    • 维护一个观察者列表

    • 提供添加和删除观察者的方法

    • 定义通知观察者的方法

  2. Observer(观察者接口)

    • 定义更新接口,用于接收主题通知

  3. ConcreteSubject(具体主题)

    • 实现主题接口

    • 存储具体状态

    • 状态改变时通知所有观察者

  4. ConcreteObserver(具体观察者)

    • 实现观察者接口

    • 维护对具体主题的引用(可选)

    • 实现更新逻辑以保持与主题状态一致

1.2 UML类图

+----------------+       +----------------+
|    Subject     |       |    Observer    |
+----------------+       +----------------+
| +attach(o)     |<>-----| +update()      |
| +detach(o)     |       +----------------+
| +notify()      |               ^
+----------------+               |^                         ||                         |
+----------------+       +----------------+
| ConcreteSubject|       | ConcreteObserver|
+----------------+       +----------------+
| +getState()    |       | +update()      |
| +setState()    |       +----------------+
+----------------+

二、观察者模式的实现

2.1 经典实现

让我们通过一个新闻发布系统的例子来展示观察者模式的经典实现:

// 观察者接口
interface NewsObserver {void update(String news);
}// 主题接口
interface NewsPublisher {void subscribe(NewsObserver observer);void unsubscribe(NewsObserver observer);void notifyObservers();
}// 具体主题
class CNN implements NewsPublisher {private List<NewsObserver> subscribers = new ArrayList<>();private String latestNews;public void setLatestNews(String news) {this.latestNews = news;notifyObservers();}@Overridepublic void subscribe(NewsObserver observer) {subscribers.add(observer);}@Overridepublic void unsubscribe(NewsObserver observer) {subscribers.remove(observer);}@Overridepublic void notifyObservers() {for (NewsObserver observer : subscribers) {observer.update(latestNews);}}
}// 具体观察者
class NewsSubscriber implements NewsObserver {private String name;public NewsSubscriber(String name) {this.name = name;}@Overridepublic void update(String news) {System.out.println(name + " received breaking news: " + news);}
}// 使用示例
public class Main {public static void main(String[] args) {CNN cnn = new CNN();NewsObserver subscriber1 = new NewsSubscriber("John");NewsObserver subscriber2 = new NewsSubscriber("Alice");cnn.subscribe(subscriber1);cnn.subscribe(subscriber2);cnn.setLatestNews("Global tech summit announces AI breakthroughs");}
}

2.2 Java内置实现

Java标准库中提供了java.util.Observable类和java.util.Observer接口,可以简化观察者模式的实现:

import java.util.Observable;
import java.util.Observer;// 被观察者
class WeatherStation extends Observable {private float temperature;public void setTemperature(float temperature) {this.temperature = temperature;setChanged(); // 标记状态已改变notifyObservers(temperature); // 通知观察者}
}// 观察者
class TemperatureDisplay implements Observer {private String location;public TemperatureDisplay(String location) {this.location = location;}@Overridepublic void update(Observable o, Object arg) {System.out.printf("[%s] Current temperature: %.1f°C\n", location, (Float)arg);}
}// 使用示例
public class WeatherApp {public static void main(String[] args) {WeatherStation station = new WeatherStation();Observer display1 = new TemperatureDisplay("Living Room");Observer display2 = new TemperatureDisplay("Bedroom");station.addObserver(display1);station.addObserver(display2);// 模拟温度变化station.setTemperature(23.5f);station.setTemperature(22.8f);}
}

三、观察者模式的深入分析

3.1 推模型 vs 拉模型

观察者模式有两种主要的实现方式:

  1. 推模型(Push Model)

    • 主题将详细的变化数据推送给观察者

    • 观察者被动接收数据

    • 实现简单,但可能推送不必要的数据

  2. 拉模型(Pull Model)

    • 主题仅通知观察者状态已改变

    • 观察者主动从主题拉取所需数据

    • 更灵活,观察者可以决定需要什么数据

    • 但增加了观察者与主题的耦合

3.2 线程安全问题

在多线程环境中使用观察者模式时需要考虑:

  1. 主题状态变更和通知的原子性

  2. 观察者列表的线程安全

  3. 观察者更新方法的执行线程

解决方案包括:

  • 使用synchronized关键字

  • 使用并发集合如CopyOnWriteArrayList

  • 使用事件总线或消息队列

3.3 观察者模式的优缺点

优点

  1. 松耦合:主题和观察者之间抽象耦合,可以独立变化

  2. 动态关系:可以在运行时动态添加或删除观察者

  3. 广播通信:支持一对多的通知机制

  4. 开闭原则:新增观察者无需修改主题代码

缺点

  1. 通知顺序不可控:观察者接收通知的顺序不确定

  2. 性能问题:大量观察者或复杂更新逻辑可能导致性能瓶颈

  3. 循环依赖:不当使用可能导致观察者之间循环调用

  4. 内存泄漏:观察者未正确注销可能导致内存泄漏

四、观察者模式的应用场景

观察者模式广泛应用于以下场景:

4.1 GUI事件处理

几乎所有现代GUI框架都基于观察者模式:

// Java Swing示例
JButton button = new JButton("Click me");
button.addActionListener(e -> {System.out.println("Button was clicked!");
});

4.2 发布-订阅系统

消息队列(如Kafka、RabbitMQ)是观察者模式的扩展实现:

// 伪代码示例
MessageBroker broker = new MessageBroker();// 发布者
broker.publish("news", "Breaking news content");// 订阅者
broker.subscribe("news", message -> {System.out.println("Received news: " + message);
});

4.3 数据监控与报警系统

class ServerMonitor extends Observable {private double cpuUsage;public void checkStatus() {// 模拟获取CPU使用率cpuUsage = Math.random() * 100;if (cpuUsage > 80) {setChanged();notifyObservers(cpuUsage);}}
}class AlertSystem implements Observer {@Overridepublic void update(Observable o, Object arg) {System.out.printf("ALERT: High CPU usage detected: %.1f%%\n", (Double)arg);}
}

4.4 MVC架构

模型(Model)变化时自动更新视图(View):

class UserModel extends Observable {private String name;public void setName(String name) {this.name = name;setChanged();notifyObservers();}
}class UserView implements Observer {@Overridepublic void update(Observable o, Object arg) {UserModel model = (UserModel)o;System.out.println("View updated: User name is now " + model.getName());}
}

五、观察者模式的变体与相关模式

5.1 发布-订阅模式

观察者模式的增强版,通过消息代理解耦发布者和订阅者:

  • 发布者不知道订阅者的存在

  • 支持更灵活的消息过滤和路由

  • 通常用于分布式系统

5.2 事件总线/事件驱动架构

集中管理事件和监听器:

// 伪代码示例
EventBus bus = new EventBus();// 发布事件
bus.post(new OrderCreatedEvent(order));// 订阅事件
@Subscribe
public void handleOrderCreated(OrderCreatedEvent event) {// 处理订单创建事件
}

5.3 中介者模式

与观察者模式的区别:

  • 中介者知道所有组件

  • 组件之间不直接通信

  • 更适合复杂的交互关系

六、最佳实践与注意事项

  1. 避免过度使用:不是所有状态变化都需要观察者模式

  2. 考虑性能影响:大量观察者或复杂更新逻辑可能成为瓶颈

  3. 处理异常:观察者更新时抛出异常不应影响其他观察者

  4. 防止内存泄漏:及时移除不再需要的观察者

  5. 考虑线程安全:多线程环境下的正确同步

  6. 文档化依赖关系:明确记录主题与观察者之间的关系

七、现代语言中的观察者模式

7.1 JavaScript中的观察者模式

// 简单的观察者实现
class Observable {constructor() {this.observers = [];}subscribe(fn) {this.observers.push(fn);}unsubscribe(fn) {this.observers = this.observers.filter(subscriber => subscriber !== fn);}notify(data) {this.observers.forEach(observer => observer(data));}
}// 使用示例
const newsObservable = new Observable();const logger = news => console.log(`New article: ${news}`);
const notifier = news => alert(`Breaking news: ${news}`);newsObservable.subscribe(logger);
newsObservable.subscribe(notifier);newsObservable.notify("JavaScript ES2023 features released");

7.2 React中的观察者模式

React的状态管理和Hooks本质上是观察者模式的实现:

import React, { useState, useEffect } from 'react';function UserProfile() {const [user, setUser] = useState(null);// 模拟数据订阅useEffect(() => {const subscription = userService.subscribe(user => {setUser(user);});return () => subscription.unsubscribe();}, []);return (<div>{user && <p>Welcome, {user.name}</p>}</div>);
}

八、总结

观察者模式是构建松耦合、可扩展系统的强大工具。通过定义清晰的依赖关系,它使得对象之间的交互更加灵活和可维护。从GUI事件处理到复杂的分布式系统,观察者模式的应用无处不在。

然而,正如我们讨论的,观察者模式并非银弹。在实际应用中,我们需要根据具体场景权衡其优缺点,考虑性能影响、线程安全等问题。现代框架和语言通常提供了更高级的抽象(如响应式编程、事件总线等),但这些概念的核心仍然是观察者模式。

掌握观察者模式不仅能帮助我们更好地理解和设计软件系统,还能提升我们对现代框架和编程范式背后原理的认识。希望本文能为你深入理解和应用这一经典设计模式提供有价值的参考。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React强大且灵活hooks库——ahooks入门实践之常用场景hook

什么是 ahooks&#xff1f; ahooks 是一个 React Hooks 库&#xff0c;提供了大量实用的自定义 hooks&#xff0c;帮助开发者更高效地构建 React 应用。其中场景类 hooks 是 ahooks 的一个重要分类&#xff0c;专门针对特定业务场景提供解决方案。 安装 ahooks npm install …

Qt常用控件之QWidget(一)

Qt常用控件之QWidget&#xff08;一&#xff09;1.QWidget2.enabled属性2.geometry&#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Qt的学习】 &#x1f4dd;&#x1f4dd;本…

AIOT开发选型:行空板 K10 与 M10 适用场景与选型深度解析

前言 随着人工智能和物联网技术的飞速发展&#xff0c;越来越多的开发者、学生和爱好者投身于创意项目的构建。 在众多的开发板中&#xff0c;行空板 K10 和 M10 以其独特的优势脱颖而出。 本文旨在为读者提供一份详尽的行空板 K10 和 M10 对比分析&#xff0c;从适用场景、…

redis汇总笔记

语雀完整版&#xff1a; https://www.yuque.com/g/mingrun/embiys/calwqx/collaborator/join?tokensLcLnqz5Rv8hOKEB&sourcedoc_collaborator# 《Redis笔记》 Redis 一般问题 Redis内存模型&#xff08;I/O多路模型&#xff09;多路复用IO如何解释 为什么Redis要使用单线…

STM32用PWM驱动步进电机

硬件介绍&#xff1a;连线&#xff1a;注意这里stp连的是pwm脉冲&#xff0c;dir连的是方向到时候代码pwm波形就是从这里来的&#xff0c;具体接线根据你的代码来注意要点&#xff1a;步进电机和舵机驱动是不一样的&#xff0c;它是根据步长来移动的&#xff0c;所以要开一个中…

力扣25.7.10每日一题——重新安排会议得到最多空余时间 II

Description 今天这道题和昨天类似&#xff0c;只是允许顺序变化。 Solution 把会议区间视作桌子&#xff0c;空余时间视作空位&#xff0c;我们要把一个桌子移到别的空位中。 初步想法是枚举桌子&#xff0c;找一个长度大于等于桌子长度的空位移过去。看上去&#xff0c;找…

IP报文分片与重组原理及实现分析

IP报文分片与重组原理及实现分析 引用&#xff1a; ppp/net/packet/IPFragment.hppp/net/packet/IPFragment.cpp 1. IP分片原理 当IP数据包大小超过MTU&#xff08;最大传输单元&#xff09;时&#xff0c;路由器/主机将其分割为多个片段传输&#xff0c;每个片段包含&…

[python]在drf中使用drf_spectacular

安装drf_spectacular 文档 pypi链接:https://pypi.org/project/drf-spectacular/ 文档链接:https://drf-spectacular.readthedocs.io/en/latest/readme.html 安装步骤 在环境中添加 pip install drf-spectacular在setting的INSTALLED_APPS中添加 INSTALLED_APPS [# ALL…

【Datawhale AI 夏令营】 用AI做带货视频评论分析(二)

5.预训练模型跑分 回顾赛题 回顾赛题任务 挑战与难点&#xff1a; 标注数据少 ——> 半监督学习 or 数据增强 聚类分析噪点影响严重 回顾Baseline 问题&#xff1a; TF-IDF无法捕捉以下语义。聚类分析粗糙&#xff0c;未评估聚类质量。 提升方案&#xff1a; 分类任务…

SPSSPRO:数据分析市场SaaS挑战者的战略分析

目录 第一部分&#xff1a;执行摘要 第二部分&#xff1a;平台解构&#xff1a;产品、架构与用户体验 2.1 SaaS范式转移&#xff1a;架构与起源 2.2 功能能力&#xff1a;分析师的工具箱 2.3 “智能分析”的价值主张 第三部分&#xff1a;市场渗透与受众细分 3.1 目标用户…

低版本hive(1.2.1)UDF实现清除历史分区数据

目标&#xff1a;通过UDF实现对表历史数据清除 入参&#xff1a;表名、保留天数N 一、pom文件 <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.…

C++中顶层const与底层const

在C中&#xff0c;const关键字用于定义常量&#xff0c;但它在指针和引用上下文中会产生两种不同的常量性&#xff1a;顶层const&#xff08;top-level const&#xff09;和底层const&#xff08;low-level const&#xff09;。理解它们的区别是避免编译错误和提高代码质量的关…

“SRP模型+”多技术融合在生态环境脆弱性评价模型构建、时空格局演变分析与RSEI指数生态质量评价

集成云端、桌面端等环境&#xff0c;结合遥感云计算、GIS空间分析&#xff0c;R语言统计分析的优势&#xff0c;以分析生态环境脆弱性的时空演变为主线。通过本课程的学习&#xff0c;您将掌握&#xff1a;第一&#xff0c;收集各类指标数据&#xff0c;构建的“生态压力度-生态…

算法学习笔记:17.蒙特卡洛算法 ——从原理到实战,涵盖 LeetCode 与考研 408 例题

在计算机科学和数学领域&#xff0c;蒙特卡洛算法&#xff08;Monte Carlo Algorithm&#xff09;以其独特的随机抽样思想&#xff0c;成为解决复杂问题的有力工具。从圆周率的计算到金融风险评估&#xff0c;从物理模拟到人工智能&#xff0c;蒙特卡洛算法都发挥着不可替代的作…

Tortoise 设置

如何关闭 Windows 下 TortoiseGit 任务栏里窗口标题的分支显示 一、引言 TortoiseGit 是一个专为团队协作设计的 Git 图形化客户端&#xff0c;旨在解决版本控制中常见的问题&#xff0c;如冲突、回滚、历史查看等。本文档是 TortoiseGit 的使用手册前言部分&#xff0c;旨在向…

[论文阅读] 人工智能 + 软件工程 | AI助力软件可解释性:从用户评论到自动生成需求与解释

AI助力软件可解释性&#xff1a;从用户评论到自动生成需求与解释 Automatic Generation of Explainability Requirements and Software Explanations From User ReviewsarXiv:2507.07344 Automatic Generation of Explainability Requirements and Software Explanations From …

C语言---自定义类型(上)(结构体类型)

结构体结构体的定义与声明结构体其实和数组一样&#xff0c;都是一些值的集合&#xff0c;只不过数组是一系类相同类型的值&#xff0c;而结构体里边的成员可以是不同的数据类型。关于它的声明&#xff0c;所用到的关键字是struct。声明的语法如下&#xff1a;struct 结构体名{…

Java观察者模式实现方式与测试方法

一、实现方式 自定义实现 通过手动定义Subject和Observer接口&#xff0c;实现一对多依赖关系&#xff1a; // 观察者接口 public interface Observer {void update(float temp, float humidity, float pressure); } // 主题接口 public interface Subject {void registerObser…

leetGPU解题笔记(1)

1.题面 题目要求 向量加法 实现一个程序&#xff0c;在GPU上对两个包含32位浮点数的向量执行逐元素加法。该程序应接受两个长度相等的输入向量&#xff0c;并生成一个包含它们和的输出向量。 实现要求 禁止使用外部库 solve函数签名必须保持不变 最终结果必须存储在向量C中 示例…

5. JVM 的方法区

1. JVM介绍和运行流程-CSDN博客 2. 什么是程序计数器-CSDN博客 3. java 堆和 JVM 内存结构-CSDN博客 4. 虚拟机栈-CSDN博客 5. JVM 的方法区-CSDN博客 6. JVM直接内存-CSDN博客 7. JVM类加载器与双亲委派模型-CSDN博客 8. JVM类装载的执行过程-CSDN博客 9. JVM垃圾回收…