设计模式八:观察者模式

文章目录

      • 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{
    @Override
    public void response() {
        System.out.println("我是具体观察者ConcreteObserver1");
    }
}

public class ConcreteObserver2 implements Observer{
    @Override
    public 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 {
    @Override
    public 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{
    @Override
    public void response(EventEnum event) {
        System.out.println("ConcreteConsumer1 已知悉 宝马公司" + event.getValue() +" 活动");
    }
}
public class ConcreteConsumer2 implements Consumer{
    @Override
    public void response(EventEnum event) {
        System.out.println("ConcreteConsumer2 已知悉 宝马公司" + event.getValue() +" 活动");
    }
}
public class ConcreteConsumer3 implements Consumer {
    @Override
    public 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{
    @Override
    public 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 {
    @Override
    public void onRegister(String name) {
        log.info("邮件: 尊敬的 " + name + " 先生/女士,恭喜注册成功");
    }
    @Override
    public void onDestory(String name) {
        log.info("邮件: 尊敬的 " + name + " 先生/女士,很遗憾,您销毁账号");
    }
}

//优惠券业务接口和实现类
public interface CouponService {
    void onRegister(String name);
}
@Slf4j
@Service
public class CouponServiceImpl implements CouponService {
    @Override
    public void onRegister(String name) {
        log.info("优惠券: 尊敬的 " + name + " 先生/女士,恭喜注册成功,赠送您100元代金券");
    }
}

//消息业务接口和实现类
public interface MessageService {
    void onDestory(String name);
}
@Slf4j
@Service
public class MessageServiceImpl implements MessageService {
    @Override
    public 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 {

    @Autowired
    EmailService emailService;
    @Autowired
    MessageService messageService;
    @Autowired
    CouponService couponService;

    @Override
    public void register(String name) {
        //执行各种校验动作,入库操作
        //doRegister(name);
        log.info(name + " 的注册逻辑......");

        //发送邮件
        emailService.onRegister(name);
        //发送优惠券
        couponService.onRegister(name);
    }

    @Override
    public 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注解,可实现一个类中订阅多个事件
    @EventListener
    public void onRegister(UserRegisterEvent userRegisterEvent) {
        log.info("邮件: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,恭喜注册成功");
    }

    @EventListener
    public void onDestory(UserRegisterEvent userRegisterEvent) {
        log.info("邮件: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,很遗憾,您销毁账号");
    }
}

//优惠券业务实现类
@Slf4j
@Service
public class CouponServiceImpl implements CouponService, ApplicationListener<UserRegisterEvent> {
    //第二种实现监听者(订阅者)的方式,实现ApplicationListener接口的onApplicationEvent方法
    @Override
    public void onApplicationEvent(UserRegisterEvent userRegisterEvent) {
        log.info("优惠券: 尊敬的 " + userRegisterEvent.getUsername() + " 先生/女士,恭喜注册成功,赠送您100元代金券");
    }
}

//消息业务实现类
@Slf4j
@Service
public class MessageServiceImpl implements MessageService, ApplicationListener<UserDestoryEvent> {
    @Override
    public void onApplicationEvent(UserDestoryEvent userDestoryEvent) {
        log.info("信息: 尊敬的 " + userDestoryEvent.getUsername() + " 先生/女士,很遗憾,您销毁账号");
    }
}
//用户业务实现类
@Slf4j
@Service
public class UserServiceImpl implements UserService, ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public void setApplicationEventPublisher(@NotNull ApplicationEventPublisher applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }
    @Override
    public void register(String name) {
        //执行各种校验动作,入库操作
        //doRegister(name);
        log.info(name + " 的注册逻辑......");

        //发布注册事件
        applicationEventPublisher.publishEvent(new UserRegisterEvent(this, name));
    }
    @Override
    public 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实现异步事件解耦业务

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

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

相关文章

docker学习入门篇

1、docker简介 docker官网&#xff1a; www.docker.com dockerhub官网&#xff1a; hub.docker.com docker文档官网&#xff1a;docs.docker.com Docker是基于Go语言实现的云开源项目。 Docker的主要目标是&#xff1a;Build, Ship and Run Any App, Anywhere(构建&…

【STL】string各种函数的应用

1.string 基本赋值操作 string assign&#xff08;string str&#xff0c;int n&#xff09; string assign&#xff08;string str,int pos,int n&#xff09; 2.string存取字符操作 (at()) 注意&#xff1a;[ ]越界不会抛出异常&#xff0c;at越界会抛出异常 3.string拼接…

Singularity 容器技术从入门到掌握

Singularity 容器技术 | 从入门到掌握 谈起容器技术&#xff0c;大家第一时间想到的肯定是最流行的功能强大的 docker。但实际上在生信领域&#xff0c;许多公共课程和公司在配置分析流程时更多使用的还是 singularity&#xff0c;这主要是为了解决我们的几个痛点&#xff1a;…

深入学习默认成员函数——c++指南

前言&#xff1a;类和对象是面向对象语言的重要概念。 c身为一门既面向过程&#xff0c;又面向对象的语言。 想要学习c&#xff0c; 首先同样要先了解类和对象。 本节就类和对象的几种构造函数相关内容进行深入的解析。 目录 类和对象的基本概念 封装 类域和类体 访问限定符…

【基于langchain + streamlit 完整的与文档对话RAG】

本地部署文档问答webdemo 支持 pdf支持 txt支持 doc/docx支持 源文档索引 你的点赞和收藏是我持续分享优质内容的动力哦~ 废话不多说直接看效果 准备 首先创建一个新环境&#xff08;选择性&#xff09; conda create -n chatwithdocs python3.11 conda activate chatwith…

数据库规范化设计案例解析

1.介绍 数据库规范化设计是数据库设计的一种重要方法&#xff0c;旨在减少数据库中的冗余数据&#xff0c;提高数据的一致性&#xff0c;确保数据依赖合理&#xff0c;从而提高数据库的结构清晰度和维护效率。规范化设计通过应用一系列的规范化规则&#xff08;或称“范式”&a…

springboot的Converter和HttpMessageConveter

Converter和HttpMessageConveter是springboot和springmvc在处理请求的时候需要用到的。但是这两者的完全是不一样的&#xff0c;作用的地方也不一样。 1&#xff0c;springboot和springmvc处理请求的流程 先来回顾一下处理请求的流程&#xff1a; 用户向服务器发送请求&#…

【C++精简版回顾】22.流迭代器(输入输出迭代器)

1.输出迭代器 1.节点&#xff0c;重载 struct student {string name;int age; }; ostream& operator<<(ostream& out,student stu) {out << stu.age << stu.name ;return out; } 2.main int main() {//输入流迭代器int array[6] { 1,2,3,4,5,6 };os…

Python批量提取Word文档表格数据

在大数据处理与信息抽取领域中&#xff0c;Word文档是各类机构和个人普遍采用的一种信息存储格式&#xff0c;其中包含了大量的结构化和半结构化数据&#xff0c;如各类报告、调查问卷结果、项目计划等。这些文档中的表格往往承载了关键的数据信息&#xff0c;如统计数据、项目…

2021年中国环境统计年鉴、工业企业污染排放数据库

《中国环境统计年鉴》是国家统计局和生态环境部及其他有关部委共同编辑完成的一本反映我国环境各领域基本情况的年度综合统计资料。收录了上一年年全国各省、自治区、直辖市环境各领域的基本数据和主要年份的全国主要环境统计数据。 内容共分为十二个部分,即:1.自然状况;2.水环…

性能测试总结 —— 工具选型篇!

本篇文章主要简单总结下性能测试工具的原理以及如何选型。性能测试和功能测试不同&#xff0c;性能测试的执行是基本功能的重复和并发&#xff0c;需要模拟多用户&#xff0c;在性能测试执行时需要监控指标参数&#xff0c;同时性能测试的结果不是那么显而易见&#xff0c;需要…

Java详解:单列 | 双列集合 | Collections类

○ 前言&#xff1a; 在开发实践中&#xff0c;我们需要一些能够动态增长长度的容器来保存我们的数据&#xff0c;java中为了解决数据存储单一的情况&#xff0c;java中就提供了不同结构的集合类&#xff0c;可以让我们根据不同的场景进行数据存储的选择&#xff0c;如Java中提…

chrome高内存占用问题

chrome号称内存杀手不是盖的&#xff0c;不设设置的话&#xff0c;经常被它内存耗尽死机是常事。以下自用方法 1 自带的memory saver chrome://settings/performance PerformanceMemory Saver When on, Chromium frees up memory from inactive tabs. This gives active tab…

删除数据表

oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 删除数据表属于数据库对象的操作 drop table 表名称; 删除 emp30 表 SQL> drop table emp30;表已删除。 上面这个语句运行后&#xff0c;就会把数据表 emp30 删除 在…

考虑局部遮阴的光伏PSO-MPPT控制MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 简介 光伏电池阵列的输出特性曲线不是线性变化的。当光伏电池遮荫时&#xff0c;产生的功 率会不断变化&#xff0c;致使光伏电池阵列的输出功率不断变化&#xff0c;其输出特性曲线呈现多峰值的现象。 多峰…

游戏免费下载平台模板源码

功能介绍 此游戏网站模板源码是专门为游戏下载站而设计的&#xff0c;旨在为网站开发者提供一个高效、易于维护和扩展的解决方案。 特点&#xff1a; 响应式设计&#xff1a;我们的模板可以自适应不同设备屏幕大小&#xff0c;从而为不同平台的用户提供最佳的浏览体验。 …

Python之Web开发初学者教程—ubuntu中配置python3

Python之Web开发初学者教程—ubuntu中配置python3 ubuntu 默认安装了python 3.6.9 安装后默认不识别python命令&#xff0c;需要在bin下创建创建链接 ln -s /usr/bin/python3.6 /usr/bin/python 同理&#xff1a;pip3 符号链接为pip ln -s /usr/bin/pip3 /usr/bin/pip 安装p…

Linux命令-使用操作

Linux命令-使用操作 目录 Linux命令-使用操作软件安装yum systemctl软链接时间IP地址、主机名域名解析虚拟机配置固定IP网络传输下载和网络请求端口端口类型端口查看 进程管理主机状态系统资源监控磁盘使用信息CPU、磁盘相关信息网络状态监控 环境变量上传下载压缩解压压缩格式…

网络通信另个角度的认识(进程间通信),端口号(为什么要有,和pid的关系,分类,如何封装,和进程的定位原理+对应关系),客户端如何拿到服务端的port

目录 另一个角度认识网络通信 端口号 引入 -- 为什么要有端口号 问题 解决 端口号和pid 举例 介绍 分类 知名端口 注册端口 动态端口 客户端如何知道服务端的端口号 封装端口号 定位原理 进程和端口号的对应关系 数据如何被上层进程读到 另一个角度认识网络…

docker + nginx打包前端镜像

项目场景&#xff1a; 前端使用angular开发&#xff0c;Dockerfile如下&#xff1a; FROM nginx:1.16.1 AS base WORKDIR /app COPY nginx.conf.template /etc/nginx/ CMD ["/bin/bash", "-c", "envsubst ${APP_VERSION} < /app/index.html > …