设计模式八:观察者模式

news2024/11/26 10:31:12

文章目录

      • 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实现异步事件解耦业务

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

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

相关文章

音视频学习笔记——c++多线程(二)

✊✊✊&#x1f308;大家好&#xff01;本篇文章是多线程系列第二篇文章&#x1f607;。首先讲解了利用mutex解决多线程数据共享问题&#xff0c;举例更好理解lock和unlock的使用方法&#xff0c;以及错误操作造成的死锁问题&#xff0c;最后讲解了lock_guard与unique_lock使用…

LEETCODE3

法一:记忆化递归 int climbStairsRecursive(int n, int* memo) {if (n < 2) {return n;}if (memo[n] > 0) {return memo[n];}memo[n] climbStairsRecursive(n - 1, memo) climbStairsRecursive(n - 2, memo);return memo[n]; }int climbStairs(int n) {int* memo (in…

QML 控件添加键盘事件

在QML中&#xff0c;可以使用Keys类型来处理键盘事件。以下是一个简单的示例&#xff0c;演示如何在QML控件中添加键盘事件&#xff1a; import QtQuick 2.12 import QtQuick.Window 2.12Window {visible: truewidth: 640height: 480title: qsTr("Hello World")Recta…

Linux的MySQL安装与卸载

安装与卸载 卸载安装配置yum源安装MySQL 声明一下本人用的Linux版本是CentOs7.9版本的。 卸载 如果我们用的云服务器&#xff0c;云服务器可能会自带MySQL或者mariadb&#xff08;其实就是MySQL的一个开源分支&#xff09;&#xff0c;如果我们不想用自带的&#xff0c;需要先…

理论学习 BatchNorm2d

import torch import torch.nn as nn# With Learnable Parameters m nn.BatchNorm2d(100) # Without Learnable Parameters m nn.BatchNorm2d(100, affineFalse) input torch.randn(20, 100, 35, 45) output m(input)print(output) print(output.shape)这段代码展示了如何使…

mybatis-plus-generator 使用 velocity 生成前后台代码

操作步骤 1&#xff09;准备mybatis-plus 生成代码的 vm文件 2&#xff09;添加依赖 mybatis-plus-generator 代码生成器的依赖 3&#xff09;执行工具方法生成代码 1、准备 mybatis-plus 生成代码的 vm文件 1&#xff09;找vm模板 去工程的 external Libraries 找到 mybati…

ES6基础6

Promise对象 Promise的含义 所谓Promise&#xff0c;简单说就是一个容器&#xff0c;里面保存着某个未来才会结束的事件&#xff08;通常是一个异步操作&#xff09;的结果。从语法上说&#xff0c;Promise是一个对象&#xff0c;从它可以获取异步操作的消息。Promise提供统一的…

深度学习笔记_8隐马尔可夫模型(HMM)

隐马尔可夫模型(Hidden Markov Model, HMM)是一种统计模型&#xff0c;在语音识别、行为识别、NLP、故障诊断等领域具有高效的性能。 HMM是关于时序的概率模型&#xff0c;描述一个含有未知参数的马尔可夫链所生成的不可观测的状态随机序列&#xff0c;再由各个状态生成观测随…

设计模式 -- 1:简单工厂模式

目录 代码记录代码部分 代码记录 设计模式的代码注意要运用到面向对象的思想 考虑到紧耦合和松耦合 把具体的操作类分开 不让其互相影响&#xff08;注意这点&#xff09; 下面是UML类图 代码部分 #include <iostream> #include <memory> // 引入智能指针的头文…

linux 模拟shell

&#x1f493;博主CSDN主页:麻辣韭菜-CSDN博客&#x1f493;   ⏩专栏分类&#xff1a;http://t.csdnimg.cn/G90eI⏪   &#x1f69a;代码仓库:Linux: Linux日常代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d;&#x1f5…

[linux] socket 非阻塞模式使用注意事项

在使用 socket 的一些 api 的时候&#xff0c;默认情况下都是阻塞模式。比如使用 tcp socket 时&#xff0c;客户端调用 connect() 创建连接&#xff0c;connect() 返回的时候要么是创建连接成功了&#xff0c;要么是出现了错误&#xff0c;反正 connect() 返回的时候结果是确定…

Unsupervised RL:METRA: Scalable Unsupervised RL with Metric-Aware Abstraction

ICLR 2024 Oral paper Intro 无监督RL旨在发现潜在的行为帮助提高下游任务效率以往方法集中于探索以及基于互信息的技能发现(skill)。然而去前者在高危复杂空间实现困难&#xff0c;后者也容易因为缺乏激励导致探索能力不足。本文提出METRA核心观点认为与其在复杂状态空间处理…

OGNL表达式

文章目录 一、简介二、快速入门三、详细使用3.1 基本数据类型3.2 对象类型3.3 List集合3.4 Set集合3.5 Map集合3.6 数组3.7 静态调用3.8 算术运算3.9 逻辑运算3.10 同时执行多个表达式3.11 位运算 Ognl使用总结 - 源链接在此 >> 一、简介 OGNL&#xff08;Object-Graph…

小兔鲜鲜项目(前端vue3)

成果图 大家喜欢给一个赞被&#xff0c; 项目地址&#xff1a;gitee 注意&#xff1a;项目克隆下去之后先运行 npm i之后安装项目插件包之后在npm run dev 运行就可以了

“成像光谱遥感技术中的AI革命:ChatGPT应用指

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力。本文重点介绍ChatGPT在遥感中的应用&#xff0c;人工智能…

python读取execl里的图片

正常的读取图片 from openpyxl import load_workbook from PIL import Imagefrom openpyxl import load_workbook wb load_workbook(rC:\Users\Administrator\Downloads\output1111.xlsx) ws wb[wb.sheetnames[0]] for image in ws._images:data image.anchor._fromif image…

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

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

家长应如何培养孩子对人工智能(AI)的兴趣?无际Ai分享

随着科技的飞速发展&#xff0c;人工智能已经成为了当今社会的重要组成部分。然而&#xff0c;在中小学阶段&#xff0c;很少有学校系统地对学生进行人工智能方面的教育。作为普通家庭的家长&#xff0c;我们可以通过一些方法来激发孩子对人工智能的兴趣&#xff0c;让他们在这…

网络工程师——2024自学

一、怎样从零开始学习网络工程师 当今社会&#xff0c;人人离不开网络。整个IT互联网行业&#xff0c;最好入门的&#xff0c;网络工程师算是一个了。 什么是网络工程师呢&#xff0c;简单来说&#xff0c;就是互联网从设计、建设到运行和维护&#xff0c;都需要网络工程师来…

第7讲:数组和函数实践:扫雷游戏

第7讲&#xff1a;数组和函数实践&#xff1a;扫雷游戏 1. 扫雷游戏分析和设计1.1 扫雷游戏的功能说明1.2 游戏的分析和设计1.2.1 数据结构的分析1.2.2 文件结构设计 2. 扫雷游戏的代码实现3. 扫雷游戏的扩展 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 • 使用控制台实现…