设计模式探索:观察者模式

1. 观察者模式

1.1 什么是观察者模式

观察者模式用于建立一种对象与对象之间的依赖关系,当一个对象发生改变时将自动通知其他对象,其他对象会相应地作出反应。
在这里插入图片描述

在观察者模式中有如下角色:

  • Subject(抽象主题/被观察者): 抽象主题角色把所有观察者对象保存在一个集合里,每个主题可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject(具体主题/具体被观察者): 该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer(抽象观察者): 观察者的抽象类,定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcreteObserver(具体观察者): 实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,存储具体观察者的有关状态,这些状态需要与具体目标保持一致。
1.2 观察者模式实现
  • 观察者
/**
 * 抽象观察者
 */
public interface Observer {
    // update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
    void update();
}

/**
 * 具体观察者
 */
public class ConcreteObserverOne implements Observer {
    @Override
    public void update() {
        // 获取消息通知,执行业务代码
        System.out.println("ConcreteObserverOne 得到通知!");
    }
}

/**
 * 具体观察者
 */
public class ConcreteObserverTwo implements Observer {
    @Override
    public void update() {
        // 获取消息通知,执行业务代码
        System.out.println("ConcreteObserverTwo 得到通知!");
    }
}
  • 被观察者
/**
 * 抽象目标类
 */
public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

/**
 * 具体目标类
 */
public class ConcreteSubject implements Subject {
    // 定义集合,存储所有观察者对象
    private ArrayList<Observer> observers = new ArrayList<>();

    // 注册方法,向观察者集合中增加一个观察者
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    // 注销方法,用于从观察者集合中删除一个观察者
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    // 通知方法
    @Override
    public void notifyObservers() {
        // 遍历观察者集合,调用每一个观察者的响应方法
        for (Observer obs : observers) {
            obs.update();
        }
    }
}
  • 测试类
public class Client {
    public static void main(String[] args) {
        // 创建目标类(被观察者)
        ConcreteSubject subject = new ConcreteSubject();

        // 注册观察者类,可以注册多个
        subject.attach(new ConcreteObserverOne());
        subject.attach(new ConcreteObserverTwo());

        // 具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
        subject.notifyObservers();
    }
}

2. 发布订阅模式与观察者模式的区别

2.1 定义上的不同

发布订阅模式属于广义上的观察者模式。

  • 发布订阅模式是最常用的一种观察者模式的实现,从解耦和重用角度来看,更优于典型的观察者模式。
2.2 两者的区别

我们来看一下观察者模式与发布订阅模式结构上的区别
在这里插入图片描述

操作流程上的区别

  • 观察者模式:数据源直接通知订阅者发生改变。
  • 发布订阅模式:数据源告诉第三方(事件通道)发生了改变,第三方再通知订阅者发生了改变。

3. 观察者模式在实际开发中的应用

3.1 实际开发中的需求场景

在我们日常业务开发中,观察者模式的一个重要作用在于实现业务的解耦。以用户注册的场景为例,假设在用户注册完成时,需要给该用户发送邮件、发送优惠券等操作,如下图所示:
在这里插入图片描述

使用观察者模式之后

在这里插入图片描述

  • UserService 在完成自身的用户注册逻辑之后,仅需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
  • 其它 Service 可以自己订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。

3.2 Spring事件机制

Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:

在这里插入图片描述

  • 事件 ApplicationEvent:通过继承它,实现自定义事件。另外,通过它的 source 属性可以获取事件timestamp 属性可以获得发生时间。
  • 事件发布者 ApplicationEventPublisher:通过它,可以进行事件的发布。
  • 事件监听器 ApplicationListener:通过实现它,进行指定类型的事件的监听。
3.3 代码实现

(1) UserRegisterEvent

  • 创建 UserRegisterEvent 事件类,继承 ApplicationEvent 类,用户注册事件。代码如下:
/**
 * 用户注册事件
 */
public class UserRegisterEvent extends ApplicationEvent {
  
    private String username;

    public UserRegisterEvent(Object source) {
        super(source);
    }

    public UserRegisterEvent(Object source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

(2) UserService (事件源+事件发布)

  • 创建 UserService 类,代码如下:
/**
 * 事件源角色+事件发布
 */
@Service
public class UserService implements ApplicationEventPublisherAware {

    private Logger logger = LoggerFactory.getLogger(getClass());
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String username){
        // 执行注册逻辑
        logger.info("[register][执行用户{}的注册逻辑]", username);
        // 发布用户注册事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));
    }
}
  • 实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入到其中。
  • 在执行完注册逻辑后,调用 ApplicationEventPublisherpublishEvent(ApplicationEvent event) 方法,发布 UserRegisterEvent 事件。

(3) 创建 EmailService

/**
 * 事件监听角色
 */
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void onApplicationEvent(UserRegisterEvent event) {
        logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());
    }
}
  • 实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。
  • 实现 onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。

(4) CouponService

@Service
public class CouponService {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener 
    public void addCoupon(UserRegisterEvent event) {
        logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());
    }
}
  • 添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent

(5) DemoController

  • 提供 /demo/register 注册接口
@RestController
@RequestMapping("/demo")
public class DemoController {

    @Autowired
    private UserService userService;

    @GetMapping("/register")
    public String register(String username) {
        userService.register(username);
        return "success";
    }
}
3.4 代码测试
  1. 执行 DemoApplication 类,启动项目。
  2. 调用 http://127.0.0.1:8080/demo/register?username=mashibing 接口,进行注册。IDEA 控制台打印日志如下:
// UserService 发布 UserRegisterEvent 事件
2023-04-19 16:49:40.628  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService       : [register][执行用户mashibing的注册逻辑]

// EmailService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService     : [onApplicationEvent][给用户(mashibing) 发送邮件]

// CouponService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService    : [addCoupon][给用户(mashibing) 发放优惠劵]

4. 观察者模式总结

1) 观察者模式的优点

  • 降低目标类和观察者之间的耦合
  • 可以实现广播机制

2) 观察者模式的缺点

  • 通知的发送会消耗一定的时间
  • 如果被观察者有循环依赖,会导致系统的崩溃

3) 观察者模式常见的使用场景

  • 一个对象的改变,需要改变其他对象的时候
  • 一个对象的改变,需要进行通知的时候

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第11章 规划过程组(二)(11.10制订进度计划)

第11章 规划过程组&#xff08;二&#xff09;11.10制订进度计划&#xff0c;在第三版教材第395~397页&#xff1b;文字图片音频方式 第一个知识点&#xff1a;定义及作用 分析活动顺序、持续时间、资源需求和进度制约因素&#xff0c;创建项目进度模型&#xff0c;从而落实项目…

六、数据可视化—Wordcloud词云(爬虫及数据可视化)

六、数据可视化—Wordcloud词云&#xff08;爬虫及数据可视化&#xff09; 也是一个应用程序 http://amueller.github.io/word_cloud/ Wordcloud词云&#xff0c;在一些知乎&#xff0c;论坛等有这样一些东西&#xff0c;要么做封面&#xff0c;要么做讲解&#xff0c;进行分析…

Java并发/多线程CompleteableFuture详解

目录 CompleteableFuture 创建 获得结果的方法 辅助方法 allOf和anyOf的区别 CompletableFuture 里大约有五十种方法&#xff0c;但是可以进行归类: 变换类 thenApply 消费类 thenAccept 执行操作类 thenRun thenApply/thenAccept/thenRun 结合转化类 thenCombine 结…

浅析Nginx技术:开源高性能Web服务器与反向代理

什么是Nginx&#xff1f; Nginx是一款轻量级、高性能的HTTP和反向代理服务器&#xff0c;也可以用作邮件代理服务器。它最初由俄罗斯的程序员Igor Sysoev在2004年开发&#xff0c;并于2004年首次公开发布。Nginx的主要优势在于其非阻塞的事件驱动架构&#xff0c;能够处理大量并…

python-24-零基础自学python while循环+交互+数据的存储

学习内容&#xff1a;《python编程&#xff1a;从入门到实践》第二版 知识点&#xff1a; 文件处理 with open&#xff08;&#xff09;while 练习内容&#xff1a;10章练习题10-3、10-4、10-5 练习10-3&#xff1a;访客 编写一个程序&#xff0c;提示用户输入名字。用户做…

北森锐途人才竞聘盘点管理测评:高管领导力六大评判标准深度解析万达商管中国绿发等

北森锐途人才管理测评&#xff1a;高管领导力评判标准深度解析 在企业高管的盘点与竞聘测评领域&#xff0c;众多管理人才面临评估自身领导力的挑战。面对能力卓越、职级显赫的同僚&#xff0c;许多管理者感到缺乏一套权威且专业的评价体系。然而&#xff0c;无论是天赋异禀的领…

【回溯 - 1】46. 全排列

46. 全排列 难度&#xff1a;中等 力扣地址&#xff1a;https://leetcode.cn/problems/permutations/description/ 问题描述 给定一个 不含重复数字 的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nu…

ICMP隧道

后台私信找我获取工具 目录 ICMP隧道作用 ICMP隧道转发TCP上线MSF 开启服务端 生成后门木马 msf开启监听 开启客户端icmp隧道 执行后门木马&#xff0c;本地上线 ICMP隧道转发SOCKS上线MSF 开启服务端 生成后门木马 msf开启监听 开启客户端icmp隧道 ​执行后…

经常用借呗和花呗对征信有影响吗?

说起支付宝里的花呗和借呗&#xff0c;大伙儿肯定都不陌生&#xff0c;它们俩就像是支付宝里的信用贷款双胞胎&#xff0c;名字相近&#xff0c;性格却大相径庭。现在&#xff0c;这俩兄弟都乖乖地接入了央行的征信大家庭&#xff0c;你的每一次使用&#xff0c;都会被记录得清…

老师怎么快速发布成绩?

期末考试的钟声刚刚敲响&#xff0c;成绩单的发放却成了老师们的一大难题。每当期末成绩揭晓&#xff0c;老师们便要开始一项繁琐的任务——将每一份成绩单逐一私信给家长。这不仅耗费了大量的时间和精力&#xff0c;也让本就忙碌的期末工作变得更加繁重。然而&#xff0c;随着…

生产力工具|Endnote X9如何自动更新文件信息

一、以EndNote X9.2版本为例&#xff0c;打开EndNote文献管理软件。 二、在菜单栏找到“Edit→Preferences...”&#xff0c;点击打开&#xff0c;弹出一个“EndNote Preferences”窗口。 三、进行设置 在打开的窗口左侧选择“PDF Handing”&#xff0c;右边会出现自动导入文献…

SwiftUI知识点(二)

Animation import SwiftUIstruct AnimationBootcamp: View {State var isAnimation: Bool falsevar body: some View {VStack{Button("Button"){withAnimation(Animation.default//重复//autoreverses: true&#xff1a;A-B-A-B//false: A-B&#xff0c;A-B.repeat…

[图解]SysML和EA建模住宅安全系统-13-时间图

1 00:00:00,480 --> 00:00:02,280 首先&#xff0c;我们来看&#xff0c;图画在哪里 2 00:00:02,290 --> 00:00:04,380 这个图 3 00:00:04,390 --> 00:00:06,180 你看&#xff0c;它是描述&#xff0c;刚才讲的 4 00:00:06,190 --> 00:00:09,010 描述这个活动 …

STM32学习历程(day5)

EXTI外部中断 中断 中断就是在主程序运行过程中 出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;CPU会暂停当前的程序&#xff0c;去处理中断程序 处理完会返回被暂停的位置 继续运行原来的程序。 中断优先级 当有多个中断源同时申请中断时 CPU会根据…

设计模式之职责链模式(Chain of Responsibility Pattern)

1.概念 职责链模式&#xff08;Chain of Responsibility Pattern&#xff09;&#xff1a;避免将请求发送者与接收者耦合在一起&#xff0c;让多个对象都有机会接收请求&#xff0c;将这些对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有对象处理它为止…

单例模式(大话设计模式)C/C++版本

单例模式 C 饿汉 /* HM hungry man 饿汉 */ #include <iostream> using namespace std; class Singleton { private:Singleton() { cout << "单例对象创建&#xff01;" << endl; };Singleton(const Singleton &);Singleton &operator(c…

【ARMv8/v9 GIC 系列 2.4 -- GIC SGI 和 PPI 中断的启用配置】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC SGI 和 PPI 中断的使能配置GICR_ISENABLER0 操作使用举例SummaryGIC SGI 和 PPI 中断的使能配置 GICR_ISENABLER0寄存器(中断设置-使能寄存器0)用于启用相应的SGI(软件生成中断)或PPI(专用外设中断)向CPU接口的转发。每个…

Android多开应用软件系统设计

设计一个支持Android多开应用的软件系统&#xff0c;主要涉及到以下几个关键技术点和设计考虑&#xff1a; 1. 虚拟化技术 容器技术&#xff1a;与传统的虚拟机不同&#xff0c;可以采用更轻量级的容器技术&#xff0c;为每个应用实例创建独立的运行环境。这包括分配独立的用…

atcoder 357 F Two Sequence Queries (线段树板子)

题目&#xff1a; 分析&#xff1a; 线段树 代码&#xff1a; // Problem: F - Two Sequence Queries // Contest: AtCoder - SuntoryProgrammingContest2024&#xff08;AtCoder Beginner Contest 357&#xff09; // URL: https://atcoder.jp/contests/abc357/tasks/abc357_…

AI实时免费在线图片工具6:以图生相似图

1、以图生图&#xff0c;生成相似图 https://huggingface.co/spaces/diffusers/unofficial-SDXL-Turbo-i2i-t2i 间接实现&#xff1a;可以是图片先提取描述&#xff0c;再通过描述再去生成新图片 https://huggingface.co/spaces/gokaygokay/KolorsPlusPlus