设计模式八:观察者模式

文章目录

      • 1、观察者模式
      • 2、示例
      • 3、spring中的观察者模式
        • 3.1 spring观察者模式的使用
        • 3.2 spring观察者模式原理解析

1、观察者模式

观察者模式(Observer Design Pattern),也叫做发布订阅模式(Publish-Subscribe Design Pattern)、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式

观察者允许一个对象将其状态的改变通知其他对象。实际上主要的部分就是观察者和被观察者,比如前言提到的消息发布,就属于被观察者,而各种不同的平台消息提醒,则是一系列的观察者。

模型:
8.1、观察者模型

//观察者接口
public interface Observer {/**声明响应方法,被观察者调用以达到通知的作用*/void response();
}//观察者实现类
public class ConcreteObserver1 implements Observer{@Overridepublic void response() {System.out.println("我是具体观察者ConcreteObserver1");}
}public class ConcreteObserver2 implements Observer{@Overridepublic void response() {System.out.println("我是具体观察者ConcreteObserver2");}
}
//观察者抽象类
public abstract class Subject {//存储注册的观察者protected List<Observer> observerList = new ArrayList<Observer>();/*** 增加观察者* @param observer 观察者*/public void add(Observer observer) {observerList.add(observer);}/*** 注销观察者,从观察者集合中删除一个观察者* @param observer 观察者*/public void remove(Observer observer) {observerList.remove(observer);}/**通知观察者*/public abstract void notifyObserver();}//被观察者实现类
public class ConcreteSubject extends Subject {@Overridepublic void notifyObserver() {System.out.println("遍历观察者:");for (Observer observer : observerList) {observer.response();}}
}
//客户端测试类
public class Client {public static void main(String[] args) {Subject subject = new ConcreteSubject();//项目中,这些内容一般在new Subject对象启动时自动加载subject.add(new ConcreteObserver1());subject.add(new ConcreteObserver2());subject.notifyObserver();}
}

观察者注册到被观察者中,然后通过被观察者调用观察者的方法达到通知的效果

实际中,会有多个观察者,以及多个事件,每个观察者关注不同的事件,待相对应事件发生时,"通知"关注改时间的观察者

2、示例

宝马公司偶尔推出打折或立减活动(8折、9折、立减5000),不同的用户关注不同的活动,如用户1关注8折和9折活动,用户2关注8折和立减5000活动,用户3这些活动都关注,当宝马公司推出一种活动的时候,立刻通知到关注该活动的用户

8.2、观察者模式示例

//活动(事件)
@Getter
public enum EventEnum {eightDisc(1, "8折"),nineDisc(2, "9折"),subFiveThous(3, "立减5000元");private Integer code;private String  value;public static EventEnum getEventEnum(Integer code){for(EventEnum event : values()){if(event.code.equals(code)){return event;}}return null;}public static EventEnum getEventEnum(String value){for(EventEnum event : values()){if(event.getValue().equals(value)){return event;}}return null;}public String getValue(Integer code){for(EventEnum event : values()){if(event.code.equals(code)){return event.getValue();}}return null;}public Integer getCode(String value){for(EventEnum event : values()){if(event.getValue().equals(value)){return event.getCode();}}return null;}EventEnum(Integer code, String value) {this.code = code;this.value = value;}public void setCode(Integer code) {this.code = code;}public void setValue(String value) {this.value = value;}
}
//客户(观察者)
public interface Consumer {void response(EventEnum event);
}
public class ConcreteConsumer1 implements Consumer{@Overridepublic void response(EventEnum event) {System.out.println("ConcreteConsumer1 已知悉 宝马公司" + event.getValue() +" 活动");}
}
public class ConcreteConsumer2 implements Consumer{@Overridepublic void response(EventEnum event) {System.out.println("ConcreteConsumer2 已知悉 宝马公司" + event.getValue() +" 活动");}
}
public class ConcreteConsumer3 implements Consumer {@Overridepublic void response(EventEnum event) {System.out.println("ConcreteConsumer3 已知悉 宝马公司" + event.getValue() +" 活动");}
}
//宝马公司(被观察者)
public abstract class BMWCompany {Map<EventEnum, List<Consumer>> eventMap = new HashMap<>();//客户注册关注事件public BMWCompany(){Consumer consumer1 = new ConcreteConsumer1();Consumer consumer2 = new ConcreteConsumer2();Consumer consumer3 = new ConcreteConsumer3();registerConsumerEvent(consumer1, EventEnum.getEventEnum(1));registerConsumerEvent(consumer1, EventEnum.getEventEnum(2));registerConsumerEvent(consumer2, EventEnum.getEventEnum(2));registerConsumerEvent(consumer2, EventEnum.getEventEnum(3));registerConsumerEvent(consumer3, EventEnum.getEventEnum(1));registerConsumerEvent(consumer3, EventEnum.getEventEnum(2));registerConsumerEvent(consumer3, EventEnum.getEventEnum(3));}public void registerConsumerEvent(Consumer consumer,EventEnum event){List<Consumer> consumers = eventMap.getOrDefault(event, new ArrayList<>());consumers.add(consumer);eventMap.put(event, consumers);}public void removeConsumer(Consumer consumer){for(List<Consumer> consumerList : eventMap.values()){consumerList.remove(consumer);}}public abstract void notifyConsumer(EventEnum event);
}public class DiscountManage extends BMWCompany{@Overridepublic void notifyConsumer(EventEnum event) {List<Consumer> consumers = eventMap.get(event);if(!CollectionUtils.isEmpty(consumers)){consumers.forEach(consumer -> consumer.response(event));}else{System.out.println("无人关注 " + event.getValue() + " 活动");}}
}
//客户端测试类
public class Client {public static void main(String[] args) {BMWCompany discountManage = new DiscountManage();discountManage.notifyConsumer(EventEnum.getEventEnum(3));}
}

该示例中,生成被观察者对象时,构造函数将观察者与其关注的事件放入到map中,key为事件,value为关注该事件的用户;

也就是说由被观察者管理事件和观察者之间的关系:观察者面向被观察者,由被观察者管理;被观察者亲自通知观察者

以上就是观察者模式与发布订阅模式,发布订阅模式有专门的组件管理事件和观察者之间的关系:被观察者发布事件到事件组件,无需关心谁订阅了哪些事件;观察者面向事件组件订阅事件,不关心谁发布的事件

3、spring中的观察者模式

以上示例中我们需要在创建被观察者实例时,自己写代码创建观察者和事件之间的关系,当新增事件和观察者的时候,要在BMWCompany()构造函数中再新增语句;那么可不可以只提供事件、观察者以及观察者所关注的事件,组装的事情由spring自动完成?

3.1 spring观察者模式的使用

示例:平台用户注册成功时,为用户发送邮件并发放优惠券;平台用户销毁账户时,发送邮件和消息

一般模型:
8.3、用户注册模型

//邮件业务接口和实现类
public interface EmailService {void onRegister(String name);void onDestory(String name);
}
public class EmailServiceImpl implements EmailService {@Overridepublic void onRegister(String name) {log.info("邮件: 尊敬的 " + name + " 先生/女士,恭喜注册成功");}@Overridepublic void onDestory(String name) {log.info("邮件: 尊敬的 " + name + " 先生/女士,很遗憾,您销毁账号");}
}//优惠券业务接口和实现类
public interface CouponService {void onRegister(String name);
}
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {@Overridepublic void onRegister(String name) {log.info("优惠券: 尊敬的 " + name + " 先生/女士,恭喜注册成功,赠送您100元代金券");}
}//消息业务接口和实现类
public interface MessageService {void onDestory(String name);
}
@Slf4j
@Service
public class MessageServiceImpl implements MessageService {@Overridepublic void onDestory(String name) {log.info("信息: 尊敬的 " + name + " 先生/女士,很遗憾,您销毁账号");}
}
//用户业务接口和实现类
public interface UserService {void register(String name);void destroy(String name);
}
@Slf4j
@Service
public class UserServiceImpl implements UserService {@AutowiredEmailService emailService;@AutowiredMessageService messageService;@AutowiredCouponService couponService;@Overridepublic void register(String name) {//执行各种校验动作,入库操作//doRegister(name);log.info(name + " 的注册逻辑......");//发送邮件emailService.onRegister(name);//发送优惠券couponService.onRegister(name);}@Overridepublic void destroy(String name) {//执行销毁账号操作//doDestory(name);log.info(name + " 的销毁逻辑......");//发送邮件emailService.onDestory(name);//发送短信messageService.onDestory(name);}
}

如上所示,如果订阅者很多,那么在用户业务实现类中要添加所有的相关订阅者引用,并且在方法中通知所有对应的订阅者

spring中发布订阅模式:

8.4、spring发布订阅

//注册事件
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 class UserDestoryEvent extends ApplicationEvent {private String username;public UserDestoryEvent(Object source) {super(source);}public UserDestoryEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;}
}
//邮件业务实现类
@Slf4j
@Service
public class EmailServiceImpl implements EmailService {//实现监听者(订阅者)的一种方式---方法上添加@EventListener注解,可实现一个类中订阅多个事件@EventListenerpublic void onRegister(UserRegisterEvent userRegisterEvent) {log.info("邮件: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,恭喜注册成功");}@EventListenerpublic void onDestory(UserRegisterEvent userRegisterEvent) {log.info("邮件: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,很遗憾,您销毁账号");}
}//优惠券业务实现类
@Slf4j
@Service
public class CouponServiceImpl implements CouponService, ApplicationListener<UserRegisterEvent> {//第二种实现监听者(订阅者)的方式,实现ApplicationListener接口的onApplicationEvent方法@Overridepublic void onApplicationEvent(UserRegisterEvent userRegisterEvent) {log.info("优惠券: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,恭喜注册成功,赠送您100元代金券");}
}//消息业务实现类
@Slf4j
@Service
public class MessageServiceImpl implements MessageService, ApplicationListener<UserDestoryEvent> {@Overridepublic void onApplicationEvent(UserDestoryEvent userDestoryEvent) {log.info("信息: 尊敬的 " + userDestoryEvent.getUsername() + " 先生/女士,很遗憾,您销毁账号");}
}
//用户业务实现类
@Slf4j
@Service
public class UserServiceImpl implements UserService, ApplicationEventPublisherAware {private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(@NotNull ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}@Overridepublic void register(String name) {//执行各种校验动作,入库操作//doRegister(name);log.info(name + " 的注册逻辑......");//发布注册事件applicationEventPublisher.publishEvent(new UserRegisterEvent(this, name));}@Overridepublic void destroy(String name) {//执行销毁账号操作//doDestory(name);log.info(name + " 的销毁逻辑......");//发布销毁事件applicationEventPublisher.publishEvent(new UserDestoryEvent(this, name));}
}

由上所示,我们只需要编写事件、订阅者逻辑即可,具体的订阅者和事件之间的关系有spring来建立关联

每当事件发生时,spring获取订阅该事件的类去执行相对应的处理方法

3.2 spring观察者模式原理解析

当发布者执行applicationEventPublisher.publishEvent(new UserRegisterEvent(this, name));时,spring会一直执行到SimpleApplicationEventMulticaster.multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType)方法

public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);Executor executor = this.getTaskExecutor();Iterator var5 = this.getApplicationListeners(event, type).iterator();while(var5.hasNext()) {ApplicationListener<?> listener = (ApplicationListener)var5.next();if (executor != null) {executor.execute(() -> {this.invokeListener(listener, event);});} else {this.invokeListener(listener, event);}}
}

由上可知,spring也是通过事件获取所有关注该事件的监听器,依次执行订阅处理逻辑

具体spring执行逻辑参考 Spring观察监听器-ApplicationEventPublisher的publishEvent实现异步事件解耦业务

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

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

相关文章

适合上班族的副业:steam游戏搬砖1天3小时,月入8K

互联网新时代&#xff0c;做副业的人越来越多。如果能充分利用下班后的时间&#xff0c;还真能赚到不少钱。steam游戏搬砖项目就是这样一个非常适合上班的副业&#xff0c;只要用心去操作&#xff0c;一个月至少收入两三千&#xff0c;多的轻松上万。 steam游戏搬砖项目其实做的…

2.4 RK3399项目开发实录-使用 SD 卡升级固件(物联技术666)

通过百度网盘分享的文件&#xff1a;嵌入式物联网单片… 链接:https://pan.baidu.com/s/1Zi9hj41p_dSskPOhIUnu9Q?pwd8qo1 提取码:8qo1 复制这段内容打开「百度网盘APP 即可获取」 本文主要介绍了如何将实现使用MicroSD卡&#xff0c;更新主板上的固件。但也仅限于固件小于4G大…

RabbitMQ - 01 - 快速入门

目录 界面总览 创建队列 选择默认交换机 发布消息 查看消息 通过实现以下目标快速入门 界面总览 RabbitMQ Management 界面总览 通道: 传输消息的通道 路由: 接收和路由(分发)消息 队列: 存储消息 消息队列的流程: 生产者将消息发送给路由,路由分发消息到各个队列存储…

超级实用导出各种excel,简单粗暴,无需多言

1、加入准备的工具类 package com.ly.cloud.utils.exportUtil;import java.util.Map;public interface TemplateRenderer {Writable render(Map<String, Object> dataSource) throws Throwable;}package com.ly.cloud.utils.exportUtil;import java.util.Map;public int…

Python之禅——跟老吕学Python编程

Python之禅——跟老吕学Python编程 Python之禅1.**Beautiful is better than ugly.**2.**Explicit is better than implicit.**3.**Simple is better than complex.**4.**Complex is better than complicated.**5.**Flat is better than nested.**6.**Spare is better than den…

【Tailwind + Vue3】100行代码手写一个客服组件

【Tailwind Vue3】100行代码手写一个客服组件 通常在官网页面上&#xff0c;都会有一个在右下角的客服小组件&#xff0c;有几个按钮&#xff0c;显示电话&#xff0c;微信等信息&#xff1a; 主要有以下几个难点&#xff1a; 动态类名绑定&#xff1a; 在迭代生成的每个工具…

AI绘画怎么用?详细教程在这里!

AI绘画是一种利用人工智能技术来创作艺术作品的方式。以下是一个详细的AI绘画的详细教程&#xff0c;介绍AI绘画怎么用? 1. 选择合适的AI绘画工具&#xff1a;市面上有许多AI绘画工具供用户选择&#xff0c;如建e网AI、DeepArt、DALL-E等。用户可以根据自己的需求和兴趣&#…

Python 导入Excel三维坐标数据 生成三维曲面地形图(面) 1、线条折线曲面

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 代码: import pandas as pd import matplotlib.pyplot as plt import numpy as np from mpl_toolkits.mplot3d import Axes3D from matplotlib.colors import ListedColor…

Tab组件的编写与动态日期的函数封装

src\components\Tab\Icon.vue 底部导航栏子组件。 <template><router-link :to"path" class"tab-icon"><i class"icon">{{iconText}}</i><p class"text"><slot>{{ tabText }}</slot></…

2024.3.12 C++

1、自己封装一个矩形类(Rect)&#xff0c;拥有私有属性:宽度(width)、高度(height) 定义公有成员函数初始化函数:void init(int w, int h)更改宽度的函数:set w(int w)更改高度的函数:set h(int h)输出该矩形的周长和面积函数:void show() #include <iostream>using nam…

高项-项目整合管理

项目整合管理的目标 资源分配平衡竞争性需求研究各种备选方法裁剪过程以实现项目目标管理各个项目管理知识域之间的依赖关系 项目整合管理的过程 制定项目章程制定项目管理计划指导与管理项目工作管理项目知识监控项目工作实施整体变更控制结束项目或阶段 七个过程和五大过…

Servlet的book图书表格实现(使用原生js实现)

作业内容&#xff1a; 1 建立一个book.html,实现图书入库提交 整体参考效果如下: 数据提交后&#xff0c;以窗口弹出数据结果&#xff0c;如: 2 使用正则表达式验证ISBN为x-x-x格式&#xff0c;图书名不低于2个字符&#xff0c;作者不能为空&#xff0c;单价在【10-100】之间…

NASA和IBM联合开发的 2022 年多时相土地分类数据集

简介 美国国家航空航天局&#xff08;NASA&#xff09;和国际商业机器公司&#xff08;IBM&#xff09;合作&#xff0c;利用大规模卫星和遥感数据&#xff0c;包括大地遥感卫星和哨兵-2 号&#xff08;HLS&#xff09;数据&#xff0c;创建了地球观测人工智能基础模型。通过奉…

光电容积脉搏波PPG信号分析笔记

1.脉搏波信号的PRV分析 各类分析参数记参数 意义 公式 参数意义 线性分析 时域分析 均值MEAN 反应RR间期的平均水平 总体标准差SDNN 评估24小时长程HRV的总体变化&#xff0c; SDNN &#xff1c; 50ms 为异常&#xff0c;SDNN&#xff1e;100ms 为正常&#xff1b;…

Python 导入Excel三维坐标数据 生成三维曲面地形图(体) 5-1、线条平滑曲面且可通过面观察柱体变化(一)

环境和包: 环境 python:python-3.12.0-amd64包: matplotlib 3.8.2 pandas 2.1.4 openpyxl 3.1.2 scipy 1.12.0 代码: import pandas as pd import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from scipy.interpolate import griddata fro…

【Java探索之旅】数据类型与变量 浮点型,字符型,布尔型,字符串型

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Java成长日志 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;前言一、变量1.1 浮点型1.2 字符型变量1.3 布尔类型 二、字符串类型2.1 转化 &#x1f324…

建造家庭泳池位置选择尤为重要

建造家庭泳池位置选择尤为重要 在自家别墅庭院中建造一座游泳池是很多人的梦想&#xff0c;因为有泳池家人健身起来是非常方便的&#xff0c;但是建造泳池选择合适的位置显得尤为关键&#xff0c;因为合适的选址可以带来美观性及在泳池的日常使用维护中也起到了很重要的作用。…

Failed to load local image resource the server responded with a status of 500

在微信小程序里使用van-image时&#xff0c;加载本地图片加载不出来&#xff0c;报错信息如下 Failed to load local image resource /miniprogram_npm/vant/weapp/image/require(/images/xztp.jpg) the server responded with a status of 500 (HTTP/1.1 500 Internal Server …

python 通过代理服务器 连接 huggingface下载模型,并运行 pipeline

想在Python 代码中运行时下载模型&#xff0c;启动代理服务器客户端后 1. 检查能否科学上网 $ curl -x socks5h://127.0.0.1:1080 https://www.example.com <!doctype html> <html> <head><title>Example Domain</title><meta charset"…

前端css 纯数字或者字母 溢出不换行

问题&#xff1a;一个div元素盒子 宽度固定 内容是中文到达盒子宽度放不下时会自动换行&#xff0c;但是如果输入的事纯数字或者字母 会发现内容区会溢出 异常现象&#xff1a;11111超出div盒子 解决方案&#xff1a;添加属性 word-break: break-all; 原理&#xff1a;浏览器…