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

news2024/12/27 12:58:04

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

观察者模式

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

角色模型和结构图

在观察者模式中,只有两种主体:目标对象 (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.coloradmin.cn/o/1463021.html

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

相关文章

详解Monkey自动化测试(日志分析)

讲解如何利用monkey分析ANR、CRASH、以及monkey运行期间的系统日志。 1.log文件配置 首先对于report的日志命名需要有相应的规范&#xff0c;这里建议是将level 1的文件命名为verbose,将level 2的文件命名为error。前者主要是用来观看日志信息和事件分布信息&#xff0c;后者…

雷卯有多种400V以上的TVS产品供您选择

1.400V以上的TVS主要应用领域 1&#xff09;电力系统保护&#xff1a;在电力系统中&#xff0c;电压突然升高或突然降低可能会导致电气设备受损或故障。400V以上的TVS可用于保护变压器、发电机和其他电力设备免受过电压或电涌的影响。 2&#xff09;工业自动化&#xff1a;在…

el-table增加/编辑打开el-dialog内嵌套el-form,解决编辑重置表单不成功等问题

需求&#xff1a;在做表格的增删改查&#xff0c;其中新增和编辑弹窗都是同一个弹窗&#xff0c;之后有个重置按钮&#xff0c;需要用户输入的时候可以重置清空等。本文章解决如下问题 1.就是在编辑数据回填后点击重置表单没有清空也没有报错 2.解决清空表单和表格数据相互影响…

YOLO v9 思路复现 + 全流程优化

YOLO v9 思路复现 全流程优化 提出背景&#xff1a;深层网络的 信息丢失、梯度流偏差YOLO v9 设计逻辑可编程梯度信息&#xff08;PGI&#xff09;&#xff1a;使用PGI改善训练过程广义高效层聚合网络&#xff08;GELAN&#xff09;&#xff1a;使用GELAN改进架构 对比其他解法…

01 Linux简介

Linux背景 发展史 linux从哪来的&#xff1f;怎么发展的&#xff1f;得从UNIX说起 1968年&#xff0c;一些来自通用电气公司、贝尔实验室和麻省理工学院的研究人员开发了一个名叫Multics的特殊操作系统。Multics在多任务文件管理和用户连接中综合了许多新概念1969-1970年&am…

【Redis】深入理解 Redis 常用数据类型源码及底层实现(4.详解Hash数据结构)

Hash数据结构 看过前面的介绍,大家应该知道 Redis 的 Hash 结构的底层实现在 6 和 7 是不同的,Redis 6 是 ziplist 和 hashtable,Redis 7 是 listpack 和 hashtable。 我们先使用config get hash*看下 Redis 6 和 Redis 7 的 Hash 结构配置情况(在Redis客户端的命令行界面…

2-21算法习题总结

由于蓝桥杯的题,我不知道从怎么复制,就只能粘贴图片了 翻硬币 代码 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String start sc.next();char[] starts start.toCharArray();String end sc…

Redis面试题及核心知识点讲解

redis是单线程还是多线程&#xff1f; redis是单线程的&#xff0c;主要是指网络I/O线程。Redis的持久化&#xff0c;集群同步等操作&#xff0c;则由另外的线程来执行。 2 . redis是单线程&#xff0c;为什么处理这么快&#xff1f; Redis的大部分操作都是在内存中完成。单线…

选择VR全景行业,需要了解哪些内容?

近年来&#xff0c;随着虚拟现实、增强现实等技术的持续发展&#xff0c;VR全景消费市场得以稳步扩张。其次&#xff0c;元宇宙行业的高速发展&#xff0c;也在进一步拉动VR全景技术的持续进步&#xff0c;带动VR产业的高质量发展。作为一种战略性的新兴产业&#xff0c;国家和…

一分钟学会MobaXterm当Linux客户端使用

一、介绍 MobaXterm是一款功能强大的远程计算机管理工具&#xff0c;它集成了各种网络工具和远程连接协议&#xff0c;可以帮助用户在Windows系统上轻松管理远程计算机。MobaXterm支持SSH、Telnet、RDP、VNC等多种远程连接协议&#xff0c;同时还集成了X11服务器&#xff0c;可…

react hook使用UEditor引入秀米图文排版

里面坑比较多&#xff0c;细节也比较多 以下使用的是react 18 ice3.0&#xff0c;使用其他react脚手架的配置基本相同&#xff0c;例如umi4 1.下载UEditor 进入UEditor仓库&#xff0c;找到版本v1.4.3.3&#xff0c;点击进去 接着下载ueditor1_4_3_3-utf8-jsp.zip版本 下载好…

iMazing2024Windows和Mac的iOS设备管理软件(可以替代iTunes进行数据备份和管理)

iMazing2024是一款兼容 Windows 和 Mac 的 iOS 设备管理软件&#xff0c;可以替代 iTunes 进行数据备份和管理。以下是一些 iMazing 的主要功能和优点&#xff1a; 数据备份和恢复&#xff1a;iMazing 提供了强大的数据备份和恢复功能&#xff0c;可以备份 iOS 设备上的各种数据…

精品基于jsp+SSM实现的仓储物流管理系统-驿站快递登记取件

《[含文档PPT源码等]精品基于jspSSM实现的仓储物流管理系统[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 使用技术&#xff1a; 开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版…

使用 package.json 配置代理解决 React 项目中的跨域请求问题

使用 package.json 配置代理解决 React 项目中的跨域请求问题 当我们在开发前端应用时&#xff0c;经常会遇到跨域请求的问题。为了解决这个问题&#xff0c;我们可以通过配置代理来实现在开发环境中向后端服务器发送请求。 在 React 项目中&#xff0c;我们可以使用 package…

MyBatis-Plus:通用分页实体封装

分页查询实体&#xff1a;PageQuery package com.example.demo.demos.model.query;import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.Data; import org.springframework.util.St…

linux 系统的目录结构

为什么某些执行程序位于/bin、/sbin、/usr/bin或/usr/sbin目录下&#xff1f;例如&#xff0c;less命令位于/usr/bin目录下。为什么不是/bin、/sbin或/usr/sbin&#xff1f;这些目录之间有什么区别呢&#xff1f; 在这篇文章中&#xff0c;让我们主要讲述一下Linux文件系统结构…

css实现梯形

<div class"trapezoid"></div> .trapezoid {width: 200px;height: 0;border-bottom: 100px solid red; /* 定义梯形的底边 */border-left: 50px solid transparent; /* 定义梯形的左边 */border-right: 50px solid transparent; /* 定义梯形的右边 */} …

【测试】----JMeter性能测试工具入门篇

定义&#xff08;主要测试的是接口&#xff09; JMeter是Apache组织使用Java开发的一款测试工具&#xff0c;可以对服务器&#xff0c;网络或者对象模拟巨大的负载情况&#xff1b;通过创建带有断言的脚本来验证程序是否能返回期望的结果 优缺点 优点 开源免费跨平台&#xff0…

Sui基金会与沙迦美国大学宣布合作开设区块链学院

沙迦美国大学&#xff08;American University of Sharjah&#xff0c;AUS&#xff09;作为中东领先的创新中心&#xff0c;与Sui合作推出了一个新的区块链研究中心&#xff0c;进一步提升了其在创新领域的声誉。 该举措是沙迦酋长国旨在将自己定位为教育和研究区域中心的一部…

imazing是什么软件?2024最新版本有哪些功能

iMazing的其他功能包括&#xff1a; iOS信息查询&#xff1a;在iMazing中&#xff0c;用户可以查询到关于iOS设备的所有信息&#xff0c;包括设备型号、系统版本、存储容量、电池状态等&#xff0c;还能将这些设备的信息进行导出&#xff0c;方便用户随时了解自己的设备状态。…