观察者模式, 发布-订阅模式, 监听器模式

观察者模式, 发布-订阅模式, 监听器模式

观察者模式

观察者模式是一种行为型设计模式, 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

角色模型和结构图

在观察者模式中,只有两种主体:目标对象 (Object) 和 观察者 (Observer)。宗门任务大殿就是目标对象,弟子们就是观察者。

  • Subject(主题): 主题是被观察的对象,它维护了一个观察者列表,并提供了添加、删除和通知观察者的方法
  • Observer(观察者): 观察者是订阅主题对象的对象,当主题对象的状态发生变化时,观察者会接收到通知并进行相应的处理。

结构图如下
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码实现

观察者抽象接口-Observer
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 抽象观察者接口
 */
public interface Observer {
    /**
     * 发生改变时发送的message
     * @param message 发送的message
     */
    void update(Object message);
}
主题-Subject
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 主题
 */
public interface Subject {
    /**
     * 主题新增观察者
     * @param observer 要注册的Observer
     */
    void registerObserver(Observer observer);

    /**
     * 移除主题下的观察者
     * @param observer 要移除的Observer
     */
    void removeObserver(Observer observer);

    /**
     * 通知该主题所有的Observer
     * @param message 通知内容
     */
    void notifyObservers(Object message);
}
Observer实现类-User
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 微信公众号的具体观察者即用户User
 */
public class User implements Observer {

    /**
     * 用户姓名
     */
    private final String name;

    /**
     * 接受的消息
     */
    private Object message;

    public User(String name) {
        this.name = name;
    }

    /**
     * 确认消息
     */
    public void read() {
        System.out.println(name + "收到推送消息: " + message);
    }

    /**
     * @param message 发送的message
     */
    @Override
    public void update(Object message) {
        this.message = message;
        read();
    }
}

####Subject实现类- WechatServer

import java.util.ArrayList;
import java.util.List;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 微信公共号
 */
public class WechatServer implements Subject {

    /**
     * 存储Observer的列表
     */
    private final List<Observer> observerList;

    /**
     * 推送的消息
     */
    private Object message;

    public WechatServer() {
        observerList = new ArrayList<>();
    }

    /**
     * @param observer 要注册的Observer
     */
    @Override
    public void registerObserver(Observer observer) {
        // 将Observer添加到列表中
        observerList.add(observer);
    }

    /**
     * @param observer 要移除的Observer
     */
    @Override
    public void removeObserver(Observer observer) {
        // 移除observer
        if (!observerList.isEmpty()) {
            observerList.remove(observer);
        }
    }

    /**
     * @param message 通知内容
     */
    @Override
    public void notifyObservers(Object message) {
        // 遍历被观察者列表,通知每一个Observer
        for (Observer observer : observerList) {
            // 调用update进行通知
            observer.update(message);
        }
    }

    /**
     * 发送微信公众号要推送的消息
     * @param message 要发送的消息
     */
    public void setInformation(Object message) {
        this.message = message;
        System.out.println("微信服务更新消息: " + message);
        // 消息更新,通知所有观察者
        notifyObservers(message);
    }
}
测试类-ObserverModeTest
/**
 * @author whitebrocade
 * @version 1.0
 * @description: Observer测试
 */
public class ObserverModeTest {
    public static void main(String[] args) {
        WechatServer server = new WechatServer();

        Observer jack = new User("Jack");
        Observer smith = new User("Smith");
        Observer kerry = new User("Kerry");

        server.registerObserver(jack);
        server.registerObserver(smith);
        server.registerObserver(kerry);
        server.setInformation("蚁钳是蚁钳, 蟹仔是蟹仔!");

        System.out.println("----------------------------------------------");
        // 将jack从观察者集合中移除
        server.removeObserver(jack);
        server.setInformation("菜就多练, 练就不菜");
    }
}

测试结果如下
在这里插入图片描述

发布-订阅模式

大概很多人都和我一样,觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是Observer。Publisher变化时,就主动去通知Subscriber。

其实并不是。

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。之间交流通过Broker进行

角色模型和结构图

  • 发布者(Publisher):负责发布事件或消息到事件总线(Event Bus)中,让订阅者(Subscribers)可以接收到这些事件或消息。
  • EventBus(事件总线):作为发布者和订阅者之间的中介者,负责接收发布者发布的事件,并将事件分发给所有订阅者。事件总线可以是一个独立的组件或者一个消息队列
    • 这里的发布者(事件总线)为一体
  • Subscriber(订阅者): 订阅者订阅感兴趣的消息或事件,并从消息代理中接收相关的消息或事件
  • Event(事件): 事件是发布者发布的消息或事件,订阅者可以根据自己的需求选择订阅特定的事件
    在这里插入图片描述

代码实现

Event
import lombok.Data;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 抽象事件源
 */
@Data
public abstract class Event {
    /**
     * 事件名
     */
    private String name;

    /**
     * 事件信息
     */
    private Object message;

    /**
     * 事件信息
     */
    private Object type;
}
Subscriber
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 定义订阅者接口
 */
public interface Subscriber {
    /**
     * 事件触发后执行的逻辑
     * @param event 事件
     */
    void handleEvent(Event event);
}
MyEvent
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 自定义事件源
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MyEvent extends Event {
    /**
     * 触发时间
     */
    private Date triggerTime;
}
MySubscriber
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 定义具体订阅者
 */
public class MySubscriber implements Subscriber {
    /**
     * 订阅者名称
     */
    private final String name;

    public MySubscriber(String name) {
        this.name = name;
    }

    @Override
    public void handleEvent(Event event) {
        System.out.println(name + "订阅的事件: " + event.toString());
    }
}
EventBus
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 事件总线
 */
public class EventBus {
    /**
     * k-v存储订阅者名称和订阅者
     */
    private Map<String, List<Subscriber>> subscriberList = new HashMap<>();

    /**
     * 订阅事件
     * @param eventName 事件名
     * @param subscriber 订阅者
     */
    public void subscribe(String eventName, Subscriber subscriber) {
        if (! subscriberList.containsKey(eventName)) {
            subscriberList.put(eventName, new ArrayList<>());
        }
        subscriberList.get(eventName).add(subscriber);
    }

    /**
     * 发布事件
     * @param event 事件
     */
    public void publish(Event event) {
        List<Subscriber> eventSubscribers = subscriberList.get(event.getName());
        if (eventSubscribers != null) {
            for (Subscriber subscriber : eventSubscribers) {
                subscriber.handleEvent(event);
            }
        }
    }
}
测试类
import java.util.Date;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 发布-订阅模式测试类
 */
public class PublisherTest {
    public static void main(String[] args) {
        // 创建事件总线
        EventBus eventBus = new EventBus();


        // 创建订阅者
        Subscriber jack = new MySubscriber("Jack");
        Subscriber tom = new MySubscriber("Tom");

        // 订阅事件
        eventBus.subscribe("event1", jack);
        eventBus.subscribe("event2", tom);

        // 发布事件
        MyEvent event1 = new MyEvent();
        event1.setName("event1");
        event1.setTriggerTime(new Date());
        event1.setMessage("蚁钳是蚁钳, 蟹仔是蟹仔!");
        event1.setType("消息一");
        MyEvent event2 = new MyEvent();
        event2.setName("event2");
        event2.setTriggerTime(new Date());
        event2.setMessage("菜就多练, 练就不菜!");
        event2.setType("消息二");

        eventBus.publish(event1);
        eventBus.publish(event2);
    }
}

测试结果如下
在这里插入图片描述

观察者模式 和 发布-订阅模式的对比

共同点
  1. 解耦性: 两种模式都能实现发布者与订阅者(观察者)之间的解耦,使得发布者和订阅者可以独立地进行扩展和修改,互不影响。
  2. 事件通知: 在两种模式中,发布者(主题)发生变化时会通知订阅者(观察者),订阅者(观察者)会相应地处理这些事件或通知
区别
  1. 通信机制:
    • 观察者模式: 观察者模式是一对多的通信机制,一个主题对象可以有多个观察者对象订阅它,当主题对象状态发生变化时,所有订阅者都会收到通知
    • 发布-订阅模式: 发布-订阅模式是通过一个消息代理(发布者)来进行通信,发布者将消息发送到消息代理,然后由消息代理将消息分发给所有订阅者。订阅者只需订阅感兴趣的事件,而不需要直接与发布者交互
  2. 关系建立:
    • 观察者模式: 在观察者模式中,观察者需要直接订阅主题对象,主题对象需要维护一个观察者列表
    • 发布-订阅模式: 在发布-订阅模式中,发布者和订阅者之间通过一个消息代理(或事件总线Eventg)进行通信,发布者和订阅者之间不直接建立联系
  3. 灵活性:
    • 观察者模式: 观察者模式在订阅关系上是静态的,即订阅者需要直接订阅特定的主题对象
    • 发布-订阅模式: 发布-订阅模式在订阅关系上是动态的,订阅者可以根据需要订阅不同的事件或消息

监听器模式

监听器模式并不是一个新的设计模式,它是观察者模式在特定场景下的一种改造和应用。通常,观察者模式的主题在通知观察者时,通知中不包含任何信息。如果这个过程中携带了一些其他信息,那么主题本身就成为了事件源,而携带信息的封装类就成为了事件。此时的观察者模式,也就升级为监听器了。监听器模式是观察者模式的另一种形态

角色模型和结构图

监听器模式通常包含三个角色:事件源、事件对象、事件监听器

  • 事件源: 被监听的事件本身, 也就是可以触发监听器的某件事件
  • 事件对象: :事件对象里面存放了对事件源的引用, 可以通过事件对象来获取事件源, 是对事件源的包装
  • 事件监听器: 定义事件发生后的动作

代码实现

Event
import lombok.Data;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 抽象事件源
 */
@Data
public abstract class Event {
    /**
     * 事件信息
     */
    private Object message;

    /**
     * 事件信息
     */
    private Object type;
}
EventListener
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 监听器接口
 */
public interface EventListener {
    /**
     * 事件触发时回调
     * @param event 事件
     */
    void onEventReceived(Event event);
}
EventSource
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 事件源类, 用于注册监听器、触发事件并通知所有监听器
 */
public interface EventSource {

    /**
     * 注册事件监听器
     * @param listener 监听器
     */
    void addListener(EventListener listener);

    /**
     * 移除监听器
     * @param listener 监听器
     */
     void removeListener(EventListener listener);

    /**
     * 事件分发器, 将事件传递给所有注册的事件监听器
     * @param event 事件
     */
    void fireEvent(Event event);
}

MyEvent
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 自定义事件源
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MyEvent extends Event {
    /**
     * 触发时间
     */
    private Date triggerTime;
}
MyListener
/**
 * @author whitebrocade
 * @version 1.0
 * @description: 事件监听器
 */
public class MyListener implements EventListener {
    @Override
    public void onEventReceived(Event event) {
        System.out.println("收到的事件: " + event.toString());
    }
}
MyEventSource
import java.util.ArrayList;
import java.util.List;

/**
 * @author whitebrocade
 * @version 1.0
 * @description: 事件源类, 用于注册监听器、触发事件并通知所有监听器
 */
public class MyEventSource implements EventSource {
    /**
     * 事件源集合
     */
    private List<EventListener> listenerList = new ArrayList<>();

    /**
     * 注册事件监听器
     * @param listener 监听器
     */
    public void addListener(EventListener listener) {
        listenerList.add(listener);
    }

    /**
     * 移除监听器
     * @param listener 监听器
     */
    public void removeListener(EventListener listener) {
        listenerList.remove(listener);
    }

    /**
     * 事件分发器, 将事件传递给所有注册的事件监听器
     * @param event 事件
     */
    public void fireEvent(Event event) {
        for (EventListener listener : listenerList) {
            listener.onEventReceived(event);
        }
    }
}
ListenerTest
import java.util.Date;

/**
 * @author whitebrocade
 * @version 1.0
 * @date 2024/2/21 21:24
 * @description: TODO
 */
public class ListenerTest {
    public static void main(String[] args) {
        // 创建一个事件源
        EventSource eventSource = new MyEventSource();
        // 创建两个事件监听器
        MyListener listener1 = new MyListener();
        MyListener listener2 = new MyListener();
        // 将这两个事件监听器注册到事件源
        eventSource.addListener(listener1);
        eventSource.addListener(listener2);

        // 创建一个事件event
        MyEvent event = new MyEvent();
        event.setTriggerTime(new Date());
        event.setMessage("蚁钳是蚁钳, 蟹仔是蟹仔!");
        event.setType("消息一");
        // 将事件传递给事件源, 进行分发
        eventSource.fireEvent(event);

        // 从事件源中移除一个listener2事件监听器
        eventSource.removeListener(listener2);

        // 再次创建一个事件event2
        MyEvent event2 = new MyEvent();
        event2.setTriggerTime(new Date());
        event2.setMessage("菜就多练, 练就不菜!");
        event2.setType("消息二");
        // 将事件传递给事件源, 进行分发
        eventSource.fireEvent(event2);
    }
}

测试结果如下
在这里插入图片描述

参考资料

观察者模式 vs 发布订阅模式,千万不要再混淆了

观察者模式 | 菜鸟教程

23 种设计模式详解(全23种)-观察者模式

观察者 - 廖雪峰的官方网站

监听器模式和观察者模式的关系,写点你不知道的

【设计模式】-11监听者模式

【设计模式】-监听者模式和观察者模式的区别与联系_观察者模式和监听者模式的区别

观察者模式 vs 发布订阅模式

[设计模式(四) —— 观察者模式/发布订阅模式](https://blog.csdn.net/weixin_37620587/article/details/130170062#:~:text=1.什么是发布-订阅模式 1 发布-订阅模式是一种行为设计模式,它允许多个对象通过事件的发布和订阅来进行通信; 2,在这种模式中,发布者 (又称为主题)负责发布事件,而订阅者 (也称为观察者)则通过订阅主题来接收这些事件; 3 这种模式使得应用程序的不同部分能够松散耦合,并且可以动态地添加或删除订阅者;)

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

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

相关文章

⭐北邮复试刷题LCR 018. 验证回文串__双指针 (力扣119经典题变种挑战)

LCR 018. 验证回文串 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s “A man, a plan, a canal: Panama” 输出: true 解释…

【PX4SimulinkGazebo联合仿真】在Simulink中使用ROS2控制无人机沿自定义圆形轨迹飞行并在Gazebo中可视化

在Simulink中使用ROS2控制无人机沿自定义圆形轨迹飞行并在Gazebo中可视化 系统架构Matlab官方例程Control a Simulated UAV Using ROS 2 and PX4 Bridge运行所需的环境配置PX4&Simulink&Gazebo联合仿真实现方法建立Simulink模型并完成基本配置整体框架各子系统实现原理…

【Vuforia+Unity】AR05-实物3D模型识别功能实现

对于3D物体的识别&#xff0c;可以是虚拟的也可以是实物的&#xff0c;但是对于虚拟的三维模型意义不大&#xff0c;我们完全可以把三维模型放在屏幕上截一张图&#xff0c;以图片识别的方式召唤数字内容&#xff0c;不过在虚拟现实中或许有用。 因此本文探讨的技术路线主要是…

Docker之查看并获取最新Ubuntu镜像(十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【快速搞定Webpack5】修改输出文件目录及自动清理上次打包文件(五)

介绍 默认情况下webpack打包后&#xff0c;我们的图片和js等文件都会被打包到dist目录下&#xff0c;文件多了混淆在一起一方面不利于文件的查找和管理&#xff0c;另外一方面看上去也不美观。 所以今天我们学习的内容就是控制输出后的文件进入不同的目录。 一、配置 新增4…

Nginx配置组成与性能调优

目录 一、Nginx配置介绍 1. 模块组成 2. 图示 3. 相关框架 二. 配置调优 1. 全局配置 1.1 关闭版本和修改版本 1.2 修改启动的进程数 1.3 cpu与work进程绑定 1.4 pid路径 1.5 nginx进程的优先级&#xff08;work进程的优先级&#xff09; 1.6 调试work进程打开的文…

npm run dev和npm run serve两个命令的区别

npm run dev和npm run serve两个命令的区别 前端开发过程中运行Vue项目的时候&#xff0c;有时候使用npm run serve命令可以启动项目&#xff0c;有时候却会报错&#xff1b;有时候使用npm run dev命令可以启动项目&#xff0c;有时候却也会报错。是什么原因造成这种情况呢&am…

问题:Spark SQL 读不到 Flink 写入 Hudi 表的新数据,打开新 Session 才可见

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

4、电源管理入门之子系统reset

目录 1. 简介 2. consumer-驱动软件 3. provider-reset驱动 3.1 整体介绍 3.2 reset复位API说明 之前的文章电源管理入门-1关机重启详解介绍了整机SoC的重启也可以说是reset,那么子系统的reset,例如某个驱动(网卡、USB等)或者某个子系统(NPU、ISP等运行在独立的M核或…

5、电源管理入门之 arm-scmi和mailbox核间通信

目录 1. 整体架构介绍 2 Linux中reset模块 2.1 Reset consumer 2.2 Reset provider 3. Linux SCMI reset通信 3.1 SCMI reset协议初始化 3.2 SCMI reset消息收发 4. SCP中reset 4.1 固件新增module 4.2 scmi_reset_domain初始化 4.3 scmi_reset_domain消息处理 4.3…

排序算法1:冒泡排序、快速排序、插入排序

排序算法&#xff1a;交换类排序&#xff0c;插入类排序、选择类排序、归并类排序 交换类排序&#xff1a;冒泡排序、快速排序 一、冒泡排序 #include <stdio.h> #include <stdlib.h> #include <time.h> typedef int ElemType; typedef struct{ElemType *e…

linux CentOs 安装docker 推荐生产环境使用

目录 1. 在CentOs上安装docker所需的系统环境 2. 卸载旧版本 2.1 查看是否已安装docker 2.2 卸载已安装的docker 3. 安装方式 3.1 使用rpm存储库安装(推荐使用该方法) 3.2 从包中安装 4. 开始docker 1. 在CentOs上安装docker所需的系统环境 需要以下CentOS版本之一的维…

压缩感知的图像仿真(MATLAB源代码)

压缩感知是一种用于高效获取和表示信号的技术&#xff0c;它可以显著减少数据的采样和传输量&#xff0c;同时保持对信号的高质量恢复能力。在压缩感知中&#xff0c;信号被表示为其在一个稀疏基中的稀疏线性组合。通过仅使用少量的随机投影测量&#xff0c;就能够捕捉信号的大…

Vue状态管理库-Pinia

一、Pinia是什么&#xff1f; Pinia 是 Vue 的专属状态管理库&#xff0c;它允许支持跨组件或页面共享状态&#xff0c;即共享数据&#xff0c;他的初始设计目的是设计一个支持组合式API的 Vue 状态管理库&#xff08;因为vue3一个很大的改变就是组合式API&#xff09;,当然这…

【数学建模入门】

数学建模入门 数学建模需要的学科知识怎么学习数学模型如何读好一篇优秀论文数学建模赛题常见类别数学建模常见问题数学建模组队和分工数学建模准备工作 数学建模需要的学科知识 怎么学习数学模型 &#x1f4a6;推荐阅读书籍&#xff1a; 《数学建模算法与应用》&#xff0c;…

tensorboard的用法

部分测试代码&#xff1a; from torch.utils.tensorboard import SummaryWriter import numpy as np from PIL import Image import torch import cv2 as cv import matplotlib.pyplot as plt from torch import nn from torchvision import datasetsdef functiontools():writ…

ros自定义action记录

文章目录 自定义action1. 定义action文件2. 修改 package.xml3. 修改 CMakeLists.txt4. 运行 catkin build4. simple_action_server.py5. simple_action_client.py 测试 自定义action ros 版本&#xff1a;kinetic 自定义test包的文件结构如下 |-- test | |-- CMakeLists.t…

Django使用Celery异步

安装包 pip install celerypip install eventlet 1.在项目文件的根目录下创建目录结果 2. 在main.py文件中 # !/usr/bin/env python # -*-coding:utf-8 -*-""" # Author &#xff1a;skyTree # version &#xff1a;python 3.11 # Description&#…

el-table同时固定左列和右列时,出现错误情况

最近遇到一个问题,就是需求是要求表格同时固定序号列和操作列,我们用的是饿了么组件库的el-table,如下图,出现了错误情况: 解决方法就是使用doLayout方法: 如果使用了keep-alive,可以在activated里执行doLayout方法: activated() {this.$nextTick(() => {this.$ref…

Qt应用-天气预报实例

本文讲解Qt实现天气预报实例。 实现的功能 网络实时获取和显示6天的天气参数并绘制温度趋势曲线; 测试当前网络连接情况; 获得当前的IP地址的行政位置信息; 设计界面如下: 创建保存天气数据的类 #ifndef WEATHERDATA_H #define WEATHERDATA_H #include <QString>…