一文搞懂设计模式—观察者模式

news2025/6/24 16:10:53

本文已收录至Github,推荐阅读 👉 Java随想录

微信公众号:Java随想录

文章目录

    • 使用场景
    • 实现方式
      • Java对观察者模式的支持
      • Guava对观察者模式的支持
      • Spring对观察者模式的支持
    • 优缺点

观察者模式(Observer Pattern)是一种常见的行为型设计模式,用于在对象之间建立一种一对多的依赖关系。当一个对象的状态发生变化时,所有依赖它的对象都将得到通知并自动更新。

使用场景

观察者模式在许多应用中都有广泛的应用,特别是当存在对象之间的一对多关系,并且需要实时通知和更新时,观察者模式非常适用。下面列举几个典型的使用场景:

  • 消息发布/订阅系统:观察者模式可以用于构建消息发布/订阅系统,其中消息发布者充当主题(被观察者),而订阅者则充当观察者。当发布者发布新消息时,所有订阅者都会收到通知并执行相应操作。
  • 用户界面组件:在图形用户界面 (GUI) 开发中,观察者模式常被用于处理用户界面组件之间的交互。当一个组件的状态发生变化时,其他依赖该组件的组件将自动更新以反映新的状态。
  • 股票市场监控:在金融领域,观察者模式可用于实现股票市场监控系统。各个投资者可以作为观察者订阅感兴趣的股票,在股票价格变动时即时收到通知。
  • 事件驱动系统:观察者模式也常用于事件驱动系统中,如图形用户界面框架、游戏引擎等。当特定事件发生时,触发相应的回调函数并通知所有注册的观察者。

以上仅是观察者模式的一些典型使用场景,实际上,只要存在对象之间的依赖关系,并且需要实现解耦和灵活性,观察者模式都可以考虑作为一种设计方案。

实现方式

观察者模式包含以下几个核心角色:

  • 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
  • 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
  • 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
  • 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

下面是观察者模式的经典实现方式:

  1. 定义观察者接口:创建一个名为 Observer 的接口,包含一个用于接收通知的方法,例如 update()
public interface Observer {
    void update();
}
  1. 定义主题接口:创建一个名为 Subject 的接口,包含用于管理观察者的方法,如 registerObserver()removeObserver()notifyObservers()
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}
  1. 实现具体主题:创建一个具体类实现 Subject 接口,实现注册、移除和通知观察者的方法。在状态变化时调用 notifyObservers() 方法通知所有观察者。
import java.util.ArrayList;
import java.util.List;

public class ConcreteSubject implements Subject {
    private final List<Observer> observers = new ArrayList<>();
    private int state;

    public void setState(int state) {
        this.state = state;
        notifyObservers();
    }

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

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}
  1. 实现具体观察者:创建一个具体类实现 Observer 接口,实现接收通知并进行相应处理的方法。
public class ConcreteObserver implements Observer {
    private String name;
    private Subject subject;

    public ConcreteObserver(String name, Subject subject) {
        this.name = name;
        this.subject = subject;
    }

    @Override
    public void update() {
        System.out.println(name+" received notification");
    }
}
  1. 使用观察者模式:在实际代码中,我们可以创建具体的主题和观察者对象,并进行注册和触发状态变化。
public class Main {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();

        ConcreteObserver observer1 = new ConcreteObserver("Observer 1", subject);
        ConcreteObserver observer2 = new ConcreteObserver("Observer 2", subject);

        subject.registerObserver(observer1);
        subject.registerObserver(observer2);

        subject.setState(10);
        // Output:
        // Observer 1 received notification
        // Observer 2 received notification

        subject.removeObserver(observer1);

        subject.setState(20);
        // Output:
        // Observer 2 received notification
    }
}

Java对观察者模式的支持

观察者模式在Java语言中的地位非常重要。在JDK的 java.util 包中,提供 Observable 类以及 Observer 接口,它们构成了Java语言对观察者模式的支持。

使用 Observable 类以及 Observer 接口,优化之后的代码为:

// 具体观察者
public class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        // 设置每一个观察者的名字
        this.name = name;
    }

    /**
     * 当变化之后,就会自动触发该方法
     */
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof Integer) {
            System.out.println(this.name + " 观察到 state 更改为:" + arg);
        }
    }
}
// 被观察者,继承 Observable 表示可以被观察
public class ConcreteSubject extends Observable {
    private int state;

    public ConcreteSubject(int state) {
        this.setState(state);
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        // 设置变化点
        super.setChanged();
        // 状态变化,通知观察者
        super.notifyObservers(state);
        this.state = state;
    }

    @Override
    public String toString() {
        return "state:" + this.state;
    }
}
public class TestObserve {
    public static void main(String[] args) {
        // 创建被观察者
        ConcreteSubject subject = new ConcreteSubject(0);
        // 创建观察者
        ConcreteObserver ConcreteObserverA = new ConcreteObserver("观察者 A");
        ConcreteObserver ConcreteObserverB = new ConcreteObserver("观察者 B");
        ConcreteObserver ConcreteObserverC = new ConcreteObserver("观察者 C");
        // 添加可观察对象
        subject.addObserver(ConcreteObserverA);
        subject.addObserver(ConcreteObserverB);
        subject.addObserver(ConcreteObserverC);

        System.out.println(subject);
        // Output:
        // state:0
        subject.setState(1);
        // Output:
        // 观察者 C 观察到 state 更改为:1
        // 观察者 B 观察到 state 更改为:1
        // 观察者 A 观察到 state 更改为:1
        System.out.println(subject);
        // Output:
        // state:1

    }
}

Guava对观察者模式的支持

Guava 中使用 Event Bus 来实现对观察者模式的支持。

com.google.common.eventbus.EventBus 提供了以下主要方法:

  • register(Object listener):将一个对象注册为事件的监听器。
  • unregister(Object listener):从事件总线中注销一个监听器。
  • post(Object event):发布一个事件到事件总线,以便通知所有注册的监听器。
  • getSubscribers(Class<?> eventClass):返回订阅指定事件类型的所有监听器的集合。

这些方法提供了事件的注册、注销、发布和获取监听器等功能,使得开发者可以方便地使用 EventBus 进行事件驱动编程。

@Getter
@AllArgsConstructor
public class MyEvent {
    private String message;
}
@Slf4j
public class EventSubscriber {
    @Subscribe
    public void handleEvent(MyEvent event) {
        String message = event.getMessage();
        // Handle the event logic
        log.info("Received event: " + message);
    }
}
@Test
public void test() {
        EventBus eventBus = new EventBus();
        EventSubscriber subscriber = new EventSubscriber();
        eventBus.register(subscriber);

        // Publish an event
        eventBus.post(new MyEvent("Hello, World!"));
        // Output:
        // Received event: Hello, World!   
    }

Spring对观察者模式的支持

Spring 中可以使用 Spring Event 来实现观察者模式。

在Spring Event中,有一些核心的概念和组件,包括ApplicationEvent、ApplicationListener、ApplicationContext和ApplicationEventMulticaster。

  • ApplicationEvent(应用事件):
    • ApplicationEvent是Spring Event框架中的基础类,它是所有事件类的父类。
    • 通过继承ApplicationEvent,并定义自己的事件类,可以创建特定类型的事件对象。
    • 事件对象通常包含与事件相关的信息,例如状态变化、操作完成等。
  • ApplicationListener(应用监听器):
    • ApplicationListener是Spring Event框架中的接口,用于监听并处理特定类型的事件。
    • 通过实现ApplicationListener接口,并指定感兴趣的事件类型,可以创建具体的监听器。
    • 监听器可以定义在任何Spring Bean中,当所监听的事件被发布时,监听器会自动接收到该事件,并执行相应的处理逻辑。
  • ApplicationContext(应用上下文):
    • ApplicationContext是Spring框架的核心容器,它负责管理Bean的生命周期和依赖关系。
    • 在Spring Event中,ApplicationContext是事件的发布者和订阅者的容器。
    • 通过获取ApplicationContext实例,可以获取ApplicationEventPublisher来发布事件,也可以注册ApplicationListener来监听事件。
  • ApplicationEventMulticaster(事件广播器):
    • ApplicationEventMulticaster是Spring Event框架中的组件,用于将事件广播给各个监听器。
    • 它负责管理事件和监听器之间的关系,并将事件传递给对应的监听器进行处理。
    • Spring框架提供了几种实现ApplicationEventMulticaster的类,如SimpleApplicationEventMulticaster和AsyncApplicationEventMulticaster,用于支持不同的事件分发策略。

通过使用这些关键概念和组件,可以在 Spring 应用程序中实现事件驱动的编程模型。事件发布者(ApplicationEventPublisher)可以发布特定类型的事件,而订阅者(ApplicationListener)可以监听和处理已发布的事件。ApplicationContext作为容器,负责管理事件和监听器,并使用ApplicationEventMulticaster来实现事件的广播和分发。

下面是使用 Spring Event 实现观察者模式的例子:

/**
 * <p>
 * 基础事件发布类
 * </p>
 *
 */

public abstract class BaseEvent<T> extends ApplicationEvent {

    /**
     * 该类型事件携带的信息
     */
    private T eventData;

    /**
     *
     * @param source 最初触发该事件的对象
     * @param eventData 该类型事件携带的信息
     */
    public BaseEvent(Object source, T eventData) {
        super(source);
        this.eventData = eventData;
    }

    public T getEventData() {
        return eventData;
    }
}

这里定义了一个基础事件发布抽象类,所有的事件发布类都可以继承此类。

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public  class User {
    private Integer userId;
    private String userName;
}
public class UserEvent  extends BaseEvent<User>{
    private static final long serialVersionUID = 8145130999696021526L;

    public UserEvent(Object source, User user) {
        super(source,user);
    }

}
@Slf4j
@Service
public class UserListener {
    /*
     * @Async加了就是异步监听,没加就是同步(启动类要开启@EnableAsync注解)
     * 可以使用@Order定义监听者顺序,默认是按代码书写顺序
     * 如果返回类型不为void,则会被当成一个新的事件,再次发布
     * @EventListener注解在EventListenerMethodProcessor类被扫描
     * 可以使用SpEL表达式来设置监听器生效的条件
     * 监听器可以看做普通方法,如果监听器抛出异常,在publishEvent里处理即可
     */

    //@Async
    @Order(1)
    @EventListener(condition = "#userEvent.getEventData().getUserName().equals('小明')")
    public String lister1(UserEvent userEvent){
        User user =userEvent.getEventData();
        log.info(user.toString());
        return "小米";
    }

    @Async
    @Order(2)
    @EventListener
    public void lister3(UserEvent userEvent){
        log.info("监听者2");
    }
    @Async
    @Order(3)
    @EventListener
    public void lister2(String name){
        log.info("我叫:"+name);
    }
    
}
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class ObserveTest {
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Test
    public void test() {
        applicationEventPublisher.publishEvent(new UserEvent(this, new User(1, "小明")));
        // Output:
        // User(userId=1, userName=小明)
        // 我叫:小米
        // 监听者2
    }

}

IDEA 中可以直接跳转到对应的监听器。

相比于 Guava Event Bus,Spring Event 在实现观察者模式时具有以下优点:

  • 集成性:Spring Event 是 Spring 框架的一部分,可以与其他 Spring 组件(如 Spring Boot、Spring MVC 等)无缝集成。这使得在一个应用程序中使用 Spring Event 变得更加方便和统一。
  • 注解驱动:Spring Event 支持使用注解来声明事件监听器和发布事件。通过使用 @EventListener 注解,开发人员可以轻松定义事件监听器方法,并且不需要显式注册和注销监听器。

优缺点

观察者模式有以下几个优点:

  • 解耦性:观察者模式能够将主题和观察者之间的耦合度降到最低。主题与观察者之间都是松散耦合的关系,它们之间可以独立地进行扩展和修改,而不会相互影响。
  • 灵活性:通过使用观察者模式,可以动态地添加、删除和通知观察者,使系统更加灵活。无需修改主题或观察者的代码,就可以实现新的观察者加入和旧观察者离开的功能。
  • 一对多关系:观察者模式支持一对多的依赖关系,一个主题可以有多个观察者。这样可以方便地实现消息的传递和广播,当主题状态更新时,所有观察者都能得到通知。

虽然观察者模式具有许多优点,但也存在一些缺点:

  • 可能引起性能问题:如果观察者较多或通知过于频繁,可能会导致性能问题。每个观察者都需要接收通知并执行相应操作,当观察者较多时,可能会增加处理时间和系统负载。
  • 可能引起循环依赖:由于观察者之间可以相互注册,如果设计不当,可能会导致循环依赖的问题。这样会导致触发通知的死循环,造成系统崩溃或异常。
  • 顺序不确定性:在观察者模式中,观察者的执行顺序是不确定的。如果观察者之间有依赖关系,可能会产生意外的结果。

综上所述,观察者模式在许多场景下都非常有用,但在使用时需要注意性能问题、循环依赖和执行顺序等方面的考虑。

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

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

相关文章

FlashMeeting(基于FFmpeg+openCV)视频语音通讯系统

Web端体验地址&#xff1a;https://download.csdn.net/download/XiBuQiuChong/88805337 客户端下载地址&#xff1a;https://download.csdn.net/download/XiBuQiuChong/88805337 FlashMeeting(基于FFmpegopenCV)是一整套先进的以FFmpegopenCV技术为基础的视频语音通讯系统。利…

linux基础IO【文件操作】

目录 前言&#xff1a; 1.文件理解 2.C语言文件操作 2.1文件打开 2.2文件关闭 2.3文件写入 2.4文件读取 3.系统文件操作 3.1open 3.2close 3.3write 3.4read 4.访问文件本质 前言&#xff1a; 我们在学习文件操作之前先要了解文件的构成&#xff0c;文件 内容 属…

职业性格在求职应聘和跳槽中的作用

性格测试对跳槽者的影响大不大&#xff1f;首先我们要弄清楚两个问题&#xff0c;性格对我们的职业生涯又没有影响&#xff0c;性格测试是什么&#xff0c;职场中有哪些应用&#xff1f;性格可以说从生下来就有了&#xff0c;随着我们的成长&#xff0c;我们的性格也越来越根深…

Stable Diffusion教程——常用插件安装与测试(一)

前言 随着Stable Diffusion不断演进&#xff0c;越来越多的开发者开始涉足插件开发。尽管网络上存在大量教程&#xff0c;但它们通常零散分布&#xff0c;逐个学习和查找非常耗时&#xff0c;使人感觉每天都在劳累思考。这里总结了Stable Diffusion常用的插件安装与测试方法。…

【JavaScript】点击选中和取消

效果图 &#xff08;1&#xff09;选中 &#xff08;2&#xff09;取消选中 实现 <template><div class"qualitityIssues"><div style"display: flex;"><div class"course-area"><div :class"checkoutIndex …

element table 点击按钮 表格滚动条移动

需求场景&#xff1a;表格列数很多的情况下&#xff0c;不想拖拽滚动条查看明细&#xff0c;所以点击按钮直接跳转到对应的位置 代码实现&#xff1a; <template><div><div class "mytable"><el-tableid "testTable"ref "t…

Qt之条件变量QWaitCondition详解(从使用到原理分析全)

QWaitCondition内部实现结构图&#xff1a; 相关系列文章 C之Pimpl惯用法 目录 1.简介 2.示例 2.1.全局配置 2.2.生产者Producer 2.3.消费者Consumer 2.4.测试例子 3.原理分析 3.1.源码介绍 3.2.辅助函数CreateEvent 3.3.辅助函数WaitForSingleObject 3.4.QWaitCo…

计算机网络之网络安全

文章目录 1. 网络安全概述1.1 安全威胁1.1.1 被动攻击1.1.2 主动攻击 1.2 安全服务 2. 密码学与保密性2.1 密码学相关基本概念2.2 对称密钥密码体制2.2.1 DES的加密方法2.2.2.三重DES 2.3 公钥密码体制 3. 报文完整性与鉴别3.1 报文摘要和报文鉴别码3.1.1 报文摘要和报文鉴别码…

基于微信小程序的健身房私教预约系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

模型可视化

模型标题可视化 可视化工具&#xff1a;Graphiz、Netron、ZetaneEngine 7.2.2Graphiz可视化工具 Graphiz是一个由AT&T实验室启动的开源工具包&#xff0c;用于绘制DOT语言脚本描述的图形&#xff0c;使用它可以非常方便地对任何图形进行可视化。 Graphiz的使用步骤包括创…

【ChatIE】论文解读:Zero-Shot Information Extraction via Chatting with ChatGPT

文章目录 介绍ChatIEEntity-Relation Triple Extration (RE)Named Entity Recognition (NER)Event Extraction (EE) 实验结果结论 论文&#xff1a;Zero-Shot Information Extraction via Chatting with ChatGPT 作者&#xff1a;Xiang Wei, Xingyu Cui, Ning Cheng, Xiaobin W…

PyTorch使用Tricks:学习率衰减 !!

文章目录 前言 1、指数衰减 2、固定步长衰减 3、多步长衰减 4、余弦退火衰减 5、自适应学习率衰减 6、自定义函数实现学习率调整&#xff1a;不同层不同的学习率 前言 在训练神经网络时&#xff0c;如果学习率过大&#xff0c;优化算法可能会在最优解附近震荡而无法收敛&#x…

算法刷题:长度最小的子数组

长度最小的子数组 .题目链接题目详情算法原理滑动窗口定义指针进窗口判断出窗口 我的答案 . 题目链接 长度最小的子数组 题目详情 算法原理 滑动窗口 这道题,我们采用滑动窗口的思想来解决,具体步骤如图所示 定义指针 如图所示,两个指针都需要从左往右进行遍历,因此初始值…

AIGC实战——能量模型(Energy-Based Model)

AIGC实战——能量模型 0. 前言1. 能量模型1.1 模型原理1.2 MNIST 数据集1.3 能量函数 2. 使用 Langevin 动力学进行采样2.1 随机梯度 Langevin 动力学2.2 实现 Langevin 采样函数 3. 利用对比散度训练小结系列链接 0. 前言 能量模型 (Energy-based Model, EBM) 是一类常见的生…

食物厨艺展示404错误页面模板源码

食物厨艺展示404错误页面模板源码&#xff0c;HTMLCSSJSCSS,记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 蓝奏云&#xff1a;https://wfr.lanzout.com/i3uC71oj52ah…

初识数据库:探索数据的世界

初识数据库&#xff1a;探索数据的世界 1. 什么是数据库&#xff1f;2. 数据库的类型2.1 关系型数据库&#xff08;RDBMS&#xff09;2.2 非关系型数据库&#xff08;NoSQL&#xff09; 3. 为什么使用数据库&#xff1f;4. 如何选择合适的数据库&#xff1f;5. 结语 在信息技术…

ovs和ovn安装

ovn和ovs介绍 ovn架构图 CMS||-----------|-----------| | || OVN/CMS Plugin || | || | || OVN Northbound DB || | || | || ovn-northd || …

设置windows10资源管理器等的边框

Windows10默认状态下&#xff0c;资源管理器、浏览器等没有边框&#xff0c;在打开多个窗口等情况下&#xff0c;想要拖动或选择某个窗口时&#xff0c;不是很好定位到窗口标题栏。 通过&#xff1a;设置&#xff08;可以通过wini组合键打开设置&#xff09;---》个性化 ---》…

第十二章[模块]:12.4:标准库:datetime

一,官方文档: 1,文档地址 datetime --- 基本日期和时间类型 — Python 3.12.2 文档源代码: Lib/datetime.py datetime 模块提供了用于操作日期和时间的类。 在支持日期时间数学运算的同时,实现的关注点更着重于如何能够更有效地解析其属性用于格式化输出和数据操作。 感知型…

vue-自定义创建项目(六)

为什么要自定义创建项目&#xff1f; 因为VueCli默认创建的项目不能够满足我们的要求&#xff0c;比如默认的项目中没有帮我们集成路由&#xff0c;vuex&#xff0c;eslink等功能。 默认项目 自定义创建项目 流程&#xff1a; 创建项目命令&#xff1a;vue create custom_dem…