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

本文已收录至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();}@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic 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;}@Overridepublic 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 notificationsubject.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;}/*** 当变化之后,就会自动触发该方法*/@Overridepublic 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;}@Overridepublic 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:0subject.setState(1);// Output:// 观察者 C 观察到 state 更改为:1// 观察者 B 观察到 state 更改为:1// 观察者 A 观察到 state 更改为:1System.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 {@Subscribepublic void handleEvent(MyEvent event) {String message = event.getMessage();// Handle the event logiclog.info("Received event: " + message);}
}
@Test
public void test() {EventBus eventBus = new EventBus();EventSubscriber subscriber = new EventSubscriber();eventBus.register(subscriber);// Publish an eventeventBus.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)@EventListenerpublic void lister3(UserEvent userEvent){log.info("监听者2");}@Async@Order(3)@EventListenerpublic void lister2(String name){log.info("我叫:"+name);}}
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class ObserveTest {@Resourceprivate ApplicationEventPublisher applicationEventPublisher;@Testpublic 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.mzph.cn/news/687625.shtml

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

相关文章

小型洗衣机哪个牌子质量好?小型洗衣机十大排名

清洗内衣内裤这些贴身衣物确实是一件比较头疼的事&#xff0c;有的小伙子由于工作的劳累通常在洗完澡后并不喜欢直接清洗内衣内裤&#xff0c;会存上几天再扔到洗衣机里&#xff0c;这样做是很不可取的&#xff0c;因为穿过的内裤很久不洗就会滋生细菌&#xff0c;另外&#xf…

Java使用Documents4j实现Word转PDF(知识点+案例)

文章目录 前言源码获取一、认识Documents4j二、快速集成2.1、pom.xml依赖2.2、word转PDF实现项目目录WordUtils.javaDemo6.java测试效果 参考文章资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里…

Linux-系统资源管理的命令

目录 查看CPU&#xff1a;more /proc/meminfo 查看内存数据&#xff1a;free -m / free -h 查看系统版本&#xff1a;more /etc/issue 查看操作系统的类型&#xff1a;uname -a 查看主机名称&#xff1a;hostname 查看磁盘空间&#xff1a;df -h 查看某个目录空间…

【解决(几乎)任何机器学习问题】:处理分类变量篇(上篇)

这篇文章相当长&#xff0c;您可以添加至收藏夹&#xff0c;以便在后续有空时候悠闲地阅读。 本章因太长所以分为上下篇来上传&#xff0c;请敬请期待 很多⼈在处理分类变量时都会遇到很多困难&#xff0c;因此这值得⽤整整⼀章的篇幅来讨论。在本章中&#xff0c;我将 讲述不同…

快速搞懂时间序列数据平稳检验

在对时间序列数据进行分析预测时&#xff0c;平稳时间序列数据预测效果更好。所以首先要检测数据是否平稳&#xff0c;没有趋势的时间序列数据&#xff0c;我们称为平稳的&#xff0c;即随着时间的推移&#xff0c;表现出恒定的方差&#xff0c;具有恒定的自相关结构。本文介绍…

Linux 虚拟机在线热扩容分区

介绍 本教程是用于Linux虚拟机在调整虚拟硬盘大小后&#xff0c;进行在线不重启热扩容分区大小。 适用于RHEL 7以上的版本及衍生发行版。&#xff08;如Centos、Rocky Linux、Alma Linux等&#xff09; 硬盘分区在线热扩容 刷新硬盘容量 echo 1 > /sys/block/sda/device…

GIS利用不舒适指数绘制地区的生物气候舒适度图

生物气候舒适度定义了最适宜的气候条件,在这种条件下,人们感到健康和充满活力。生物气候舒适度地图对城市规划研究特别有用。温度、相对湿度和风速等要素对评估生物气候舒适度非常重要。[1] 人们已经得出了许多不同的指数来确定生物气候舒适度。在本博文中,我们将使用广泛使…

Elcomsoft 取证工具包系列:Advanced Office Password Recovery

天津鸿萌科贸发展有限公司是 Elcomsoft 系列软件授权代理商。 Advanced Office Password Recovery 是 Elcomsoft 取证工具包中的密码破解软件之一。它可以恢复、删除或规避使用各种 Office 套件创建的文档的密码。可以对 WordPerfect&#xff0c;Lotus&#xff0c;OpenOffice&…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型、扩散模型等

专属领域论文订阅 VX关注{晓理紫}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新论文。 为了答谢各位网友的支持&#xff0c;从今日起免费为…

深入解析域名短链接生成原理及其在Python/Flask中的实现策略:一篇全面的指南与代码示例

为了构建一个高效且用户友好的域名短链服务&#xff0c;我们可以将项目精简为以下核心功能板块&#xff1a; 1. 用户管理 注册与登录&#xff1a;允许用户创建账户并登录系统。 这部分内容可以参考另一片文章实现&#xff1a; 快速实现用户认证&#xff1a;使用Python和Flask…

【c++设计模式03】创建型1:简单工厂模式(Simple Factory Pattern)

【c设计模式03】创建型1&#xff1a;简单工厂模式&#xff08;Simple Factory Pattern&#xff09; 一、工厂模式二、简单工厂模式三、UML类图四、demo五、使用多态的简单工厂模式1、UML类图——使用多态2、demo——使用多态 原创作者&#xff1a;郑同学的笔记 原创地址&#x…

GAN:“左右互搏”的卷积网络,不断优化性能中

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 在一个名为“卷王”的世界里&#xff0c;有一个传奇般的存在——生成对抗网络&#xff…

解释器设计模式

解释器设计模式&#xff08;Interpreter Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种语言的文法&#xff0c;并建立一个解释器来解释该语言中的句子。这种模式通常用于需要解释或执行一种特定类型的语言的场景&#xff0c;例如编程语言的编译器或解释器、…

17. 【Linux教程】查看文件内容

前面小节介绍了文件和目录相关的操作&#xff0c;本小节介绍如何使用 file、cat、more、tail 命令在不同场景下去查看文件相关信息和内容。 1. file 查看文件类型 file 命令可以用来查看文件类型&#xff0c;还能查看文件的编码格式&#xff0c;下面列举一些 file 命令的参数&…

文件上传漏洞--Upload-labs--Pass04--.htaccess绕过

一、什么是 .htaccess 文件 1、官方解释&#xff1a; .htaccess文件主要用于控制Web服务器&#xff08;如Apache&#xff09;的配置&#xff0c;使得无需修改主服务器配置文件即可对特定目录进行访问控制和功能设置。 2、通俗解释&#xff1a; 现有一份 写有恶意代码的 .ph…

[力扣 Hot100]Day28 两数相加

题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都…

计算机网络-数据通信基础

目录 前言 一、数据通信基本概念 二、数据通信相关知识1 总结 前言 正在学习计算机网络体系&#xff0c;把每日所学的知识梳理出来&#xff0c;既能够当作读书笔记&#xff0c;又能分享出来和大家一同学习讨论。 一、数据通信基本概念 基本概念&#xff1a;信源、信道、信宿&…

【LeetCode: 589. N 叉树的前序遍历 + DFS】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

NBA2K24 精品蔡徐坤面补

NBA2K24 精品蔡徐坤面补 NBA2K23-NBA2K24通用 精品蔡徐坤面补 下载地址&#xff1a; https://www.changyouzuhao.cn/13072.html

找座位 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 在一个大型体育场内举办了一场大型活动&#xff0c;由于疫情防控的需要&#xff0c;要求每位观众的必须间隔至少一个空位才允许落座。 现在给出一排观众座位分布…