设计模式之【观察者模式】,MQ的单机实现雏形

news2024/11/20 4:35:36

文章目录

  • 一、什么是观察者模式
    • 1、观察者模式应用场景
    • 2、观察者模式的四大角色
    • 3、观察者模式优缺点
  • 二、实例
    • 1、观察者模式的一般写法
    • 2、微信公众号案例
    • 3、鼠标响应事件API案例
  • 三、实现一个异步非阻塞框架
    • 1、EventBus
    • 2、使用MQ
  • 四、源码中的观察者模式
    • 1、Observable/Observer
    • 2、Flow API
    • 3、Spring中的Event

一、什么是观察者模式

观察者模式(Observer Pattern),又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖于它的对象都会得到通知并被自动更新。属于行为型模式。

观察者模式的核心是将观察者与被观察者解耦,以类似于消息/广播发送的机制联动两者,使被观察者的变动能通知到感兴趣的观察者们,从而做出相应的响应。

1、观察者模式应用场景

在软件系统中,当系统一方行为依赖于另一方行为的变动时,可以使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。观察者模式适用于以下几种应用场景:

  • 当一个抽象模型包含两个方面的内容,其中一个方面依赖于另一个方面;
  • 其他一个或多个对象的变化依赖于另一个对象的变化;
  • 实现类似广播机制的功能,无需知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播;
  • 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

2、观察者模式的四大角色

在这里插入图片描述

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

3、观察者模式优缺点

优点:

  • 观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则;
  • 分离了表示层(观察者)和数据逻辑层(被观察者),并建立了一套触发机制,使得数据的变化可以响应到多个表示层上;
  • 实现了一对多的通讯机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。

缺点:

  • 如果观察者数量过多,则事件通知会耗时较长;
  • 事件通知呈线性关系,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件;
  • 如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。

二、实例

1、观察者模式的一般写法

//抽象观察者
public interface IObserver<E> {
    void update(E event);
}
//具体观察者
public class ConcreteObserver<E> implements IObserver<E> {
    public void update(E event) {
        System.out.println("receive event: " + event);
    }
}
//抽象主题者
public interface ISubject<E> {
    boolean attach(IObserver<E> observer);

    boolean detach(IObserver<E> observer);

    void notify(E event);
}
//具体主题者
public class ConcreteSubject<E> implements ISubject<E> {
    private List<IObserver<E>> observers = new ArrayList<IObserver<E>>();

    public boolean attach(IObserver<E> observer) {
        return !this.observers.contains(observer) && this.observers.add(observer);
    }

    public boolean detach(IObserver<E> observer) {
        return this.observers.remove(observer);
    }

    public void notify(E event) {
        for (IObserver<E> observer : this.observers) {
            observer.update(event);
        }
    }
}
public class Test {

    public static void main(String[] args) {
        // 被观察者
        ISubject<String> observable = new ConcreteSubject<String>();
        // 观察者
        IObserver<String> observer = new ConcreteObserver<String>();
        // 注册
        observable.attach(observer);
        // 通知
        observable.notify("hello");
    }

}

2、微信公众号案例

在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号。

类图如下:
在这里插入图片描述

// 定义抽象观察者类,里面定义一个更新的方法
public interface Observer {
	void update(String message);
}
// 定义具体观察者类,微信用户是观察者,里面实现了更新的方法
public class WeixinUser implements Observer {
	// 微信用户名
	private String name;
	public WeixinUser(String name) {
		this.name = name;
	}
	@Override
	public void update(String message) {
		System.out.println(name + "-" + message);
	}
}
// 定义抽象主题类,提供了attach、detach、notify三个方法
public interface Subject {
	//增加订阅者
	public void attach(Observer observer);
	//删除订阅者
	public void detach(Observer observer);
	//通知订阅者更新消息
	public void notify(String message);
}
// 微信公众号是具体主题(具体被观察者),里面存储了订阅该公众号的微信用户,并实现了抽象主题中的方法
public class SubscriptionSubject implements Subject {
	//储存订阅公众号的微信用户
	private List<Observer> weixinUserlist = new ArrayList<Observer>();
	@Override
	public void attach(Observer observer) {
		weixinUserlist.add(observer);
	}
	@Override
	public void detach(Observer observer) {
		weixinUserlist.remove(observer);
	}
	@Override
	public void notify(String message) {
		for (Observer observer : weixinUserlist) {
			observer.update(message);
		}
	}
}
// 客户端程序
public class Client {
	public static void main(String[] args) {
		SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
		//创建微信用户
		WeixinUser user1=new WeixinUser("孙悟空");
		WeixinUser user2=new WeixinUser("猪悟能");
		WeixinUser user3=new WeixinUser("沙悟净");
		//订阅公众号
		mSubscriptionSubject.attach(user1);
		mSubscriptionSubject.attach(user2);
		mSubscriptionSubject.attach(user3);
		//公众号更新发出消息给订阅的微信用户
		mSubscriptionSubject.notify("秃了也弱了的专栏更新了");
	}
}

3、鼠标响应事件API案例

做过安卓或者是桌面程序的小伙伴会对这很熟悉,鼠标点击事件也都是通过观察者模式来实现的,我们自己用代码来实现一下。

/**
 * 观察者抽象
 */
public interface EventListener {

}
import java.lang.reflect.Method;
// 事件
public class Event {
    //事件源,动作是由谁发出的
    private Object source;
    //事件触发,要通知谁(观察者)
    private EventListener target;
    //观察者给的回应
    private Method callback;
    //事件的名称
    private String trigger;
    //事件的触发事件
    private long time;

    public Event(EventListener target, Method callback) {
        this.target = target;
        this.callback = callback;
    }

    public Object getSource() {
        return source;
    }

    public Event setSource(Object source) {
        this.source = source;
        return this;
    }

    public String getTrigger() {
        return trigger;
    }

    public Event setTrigger(String trigger) {
        this.trigger = trigger;
        return this;
    }

    public long getTime() {
        return time;
    }

    public Event setTime(long time) {
        this.time = time;
        return this;
    }

    public Method getCallback() {
        return callback;
    }

    public EventListener getTarget() {
        return target;
    }

    @Override
    public String toString() {
        return "Event{" +
                "source=" + source +
                ", target=" + target +
                ", callback=" + callback +
                ", trigger='" + trigger + '\'' +
                ", time=" + time +
                '}';
    }
}

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
 * 被观察者的抽象
 */
public class EventContext {
    protected Map<String,Event> events = new HashMap<String,Event>();

    public void addLisenter(String eventType, EventListener target, Method callback){
        events.put(eventType,new Event(target,callback));
    }

    public void addLisenter(String eventType, EventListener target){
        try {
            this.addLisenter(eventType, target, target.getClass().getMethod("on" + toUpperFirstCase(eventType), Event.class));
        }catch (NoSuchMethodException e){
            return;
        }
    }

    private String toUpperFirstCase(String eventType) {
        char [] chars = eventType.toCharArray();
        chars[0] -= 32;
        return String.valueOf(chars);
    }

    private void trigger(Event event){
        event.setSource(this);
        event.setTime(System.currentTimeMillis());

        try {
            if (event.getCallback() != null) {
                //用反射调用回调函数
                event.getCallback().invoke(event.getTarget(), event);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    protected void trigger(String trigger){
        if(!this.events.containsKey(trigger)){return;}
        trigger(this.events.get(trigger).setTrigger(trigger));
    }
}

// 观察类型
public interface MouseEventType {
    String ON_CLICK = "click";

    String ON_MOVE = "move";
}

/**
 * 具体的被观察者
 */
public class Mouse extends EventContext {
    public void click(){
        System.out.println("调用单击方法");
        this.trigger(MouseEventType.ON_CLICK);
    }

    public void move(){
        System.out.println("调用移动方法");
        this.trigger(MouseEventType.ON_MOVE);
    }
}

/**
 * 观察者
 */
public class MouseEventLisenter implements EventListener {

    public void onClick(Event e){
        System.out.println("==========触发鼠标单击事件========\n" + e);
    }

    public void onMove(Event e){

        System.out.println("==========触发鼠标移动事件========\n" + e);
    }
}

// 客户端代码
public class Test {
    public static void main(String[] args) {
        MouseEventLisenter lisenter = new MouseEventLisenter();

        Mouse mouse = new Mouse();
        mouse.addLisenter(MouseEventType.ON_CLICK,lisenter);
        mouse.addLisenter(MouseEventType.ON_MOVE,lisenter);

        mouse.click();
        mouse.move();
    }
}

三、实现一个异步非阻塞框架

我们前面介绍过,观察者模式的一个弊端就是需要一个个通知,如果其中一个观察者处理事件卡壳,会影响后续的观察者接收该事件。

1、EventBus

EventBus 翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus 就是一个比较著名的 EventBus 框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。

2、使用MQ

使用MQ可以应用于分布式场景的发布订阅模型。

四、源码中的观察者模式

1、Observable/Observer

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

  • void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
  • void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
  • void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。

我们实现一个警察抓小偷的案例,警察是观察者,小偷是被观察者。代码如下:

// 小偷是一个被观察者,所以需要继承Observable类
public class Thief extends Observable {
	private String name;
	public Thief(String name) {
		this.name = name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	public void steal() {
		System.out.println("小偷:我偷东西了,有没有人来抓我!!!");
		super.setChanged(); //changed = true
		super.notifyObservers();
	}
}
// 警察是一个观察者,所以需要让其实现Observer接口
public class Policemen implements Observer {
	private String name;
	public Policemen(String name) {
		this.name = name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return name;
	}
	@Override
	public void update(Observable o, Object arg) {
		System.out.println("警察:" + ((Thief) o).getName() + ",我已经盯你很久了,你可以保持沉默,但你所说的将成为呈堂证供!!!");
	}
}
// 客户端代码
public class Client {
	public static void main(String[] args) {
		//创建小偷对象
		Thief t = new Thief("隔壁老王");
		//创建警察对象
		Policemen p = new Policemen("小李");
		//让警察盯着小偷
		t.addObserver(p);
		//小偷偷东西
		t.steal();
	}
}

2、Flow API

但是Observable和Observer在jdk1.9之后被弃用了。取而代之的是java.util.concurrent.Flow 类:
响应式编程详解,带你熟悉Reactor响应式编程

3、Spring中的Event

Spring事件详解,Spring-Event源码详解,一文搞透Spring事件管理

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

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

相关文章

pygam第3课——画图小程序

前言&#xff1a;我们前两节课已经学习了&#xff0c;界面的设计、图片的加载、那么今天我们将继续学习pygame的基础知识&#xff0c;我们的今天学习的内容是&#xff1a;鼠标滑动时坐标的实时获取、鼠标的移动事件、鼠标的点击事件、图形绘制等。希望大家能 搭建界面&#xf…

firewalld防火墙(又到了可以看日落和晚霞的日子了)

文章目录 一、firewalld概述二、firewalld和iptables的关系三、firewalld区域的概念四、firewalld数据处理流程五、firewalld检查数据包源地址的规则六、firewalld防火墙的配置种类1.运行时配置2.永久配置 七、firewalld防火墙的配置方法八、使用命令配置firewalld防火墙1.获取…

Ventoy 多合一启动盘制作工具神器 - 将多个系统 Win/PE/Linux 镜像装在1个U盘里

最近很多操作系统都纷纷发布了新版本&#xff0c;比如 Windows 11、Ubuntu、Deepin、优麒麟、CentOS、Debian 等等&#xff0c;对喜欢玩系统的人来说绝对是盛宴。 不过一般用 Rufus 等工具&#xff0c;一个 U 盘往往只能制作成一个系统的启动盘/安装盘&#xff0c;想要增加另一…

零入门kubernetes网络实战-33->基于nat+brigde+veth pair形成的跨主机的内网通信方案

《零入门kubernetes网络实战》视频专栏地址 https://www.ixigua.com/7193641905282875942 本篇文章视频地址(稍后上传) 本文主要使用的技术是 nat技术Linux虚拟网桥虚拟网络设备veth pair来实现跨主机网桥的通信 1、测试环境介绍 两台centos虚拟机 # 查看操作系统版本 cat …

VIBRO-METER VM600 IRC4 智能继电器卡

额外的继电器&#xff0c;由来自MPC4和/或AMC8卡的多达86个输入的方程驱动&#xff0c;用于需要2oo3表决等更复杂的逻辑时8个继电器&#xff0c;可配置为8个SPDT或4个DPDT使用IRC4配置器软件进行完全软件配置继电器可配置为正常通电(NE)或正常断电(NDE)&#xff0c;具有可配置的…

小航助学GESP_C++一级模拟测试试卷(含题库答题软件账号)

GESP在线模拟训练系统请点击 电子学会-全国青少年编程等级考试真题Scratch一级&#xff08;2019年3月&#xff09;在线答题_程序猿下山的博客-CSDN博客_小航答题助手 答案:A 第1题人们在使用计算机时所提到的 Windows 通常指的是&#xff08;&#xff09;。 A、操作系统B、多…

Science Bulletin:张占军教授团队提出“额叶保持,颞叶损伤” 假说解析成功认知老化

步入老年后&#xff0c;各项认知能力会逐渐衰退&#xff0c;我们把这一过程称之为认知老化。认知老化的过程与速度因人而异&#xff0c;走向阿尔茨海默病&#xff08;AD&#xff09;等认知障碍疾病为结局的属于病理认知老化&#xff0c;也就是经历轻度认知障碍阶段&#xff0c;…

【分享】PowerPoint如何设置保护和加密?

想保护自己做好的PPT&#xff0c;通常用的方法就是给PPT加密。下面我们来看看PPT加密保护方式有几种&#xff0c;具体如何操作。 打开PPT&#xff0c;点击菜单【文件】&#xff0c;再依次点击【信息】-【保护演示文稿】&#xff0c;就可以看到设置密码保护的5个选项。 选项1&a…

小航助学2022年NOC初赛图形化(小低组)(含题库答题软件账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 单选题3.0分 删除编辑 答案:D 第1题如果想要从造型库中自选一个喜欢角色&#xff0c;可以点击哪个按钮呢&#xff1f; A、①B、②C…

Go语言核心编程-流程控制

第 5 章 程序流程控制 5.1 程序流程控制介绍 在程序中&#xff0c;程序运行的流程控制决定程序是如何执行的&#xff0c;是我们必须掌握的&#xff0c;主要有三大流程控 制语句。 顺序控制分支控制循环控制 5.2 顺序控制 程序从上到下逐行地执行&#xff0c;中间没有任何判…

智行致远丨美格智能亮相IOTE 2023 第十九届上海国际物联网展

5月17~19日&#xff0c;IOTE 2023第十九届上海国际物联网展盛大举办&#xff0c;全球超过350家参展企业到场展示先进的物联网技术和产品&#xff0c;盛况空前。本届展会以“IoT构建数字经济底座”为主题&#xff0c;将IoT技术引入实体经济领域&#xff0c;促进数字化转型和智能…

深度剖析JVM调优法则:从两大特性CPU、内存出发轻松掌握调优实战技巧

1、JDK自带工具 场景一、CPU过高 CPU占用过高排查思路&#xff1a;&#xff08;查进程->查线程列表->查线程堆栈&#xff09; step1&#xff1a;通过top命令查询占用CPU情况 top p.s.shiftp(大写的P-cpu排序) shiftm(大写的M-内存排序) step2&#xff1a;通过进程pi…

调试Dynaslam: Ubuntu系统下使用VS Code进行自动化调试Dynaslam的教程,包括tasks.json和launch.json的配置

调试Dynaslam: Ubuntu系统下使用VS Code进行自动化调试Dynaslam的教程&#xff0c;包括tasks.json和launch.json的配置 修改CMakeLists.txt文件 将SET(CMAKE_BUILD_TYPE Release)修改为SET(CMAKE_BUILD_TYPE Debug)不开启编译优化&#xff0c;在编译选项中包含 -g 参数来启用…

springboot+vue冬奥会科普平台(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的冬奥会科普平台。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 &#x1f495;&#x1f495;作者&#xff1a;风歌…

探索iOS之多摄像头预览架构

在iOS13.0开始支持多摄像头预览AVCaptureMultiCamSession&#xff0c;然后iOS15.0增加支持摄像头画中画预览。在使用之前&#xff0c;我们通过isMultiCamSupported()判断是否支持多Camera同时预览。 一、Camera架构 1、Camera流水线 Camera由AVCaptureDeviceInput、AVCaptur…

【实践篇】领域驱动设计:DDD工程参考架构 | 京东云技术团队

背景 为什么要制定参考工程架构 不同团队落地DDD所采取的应用架构风格可能不同&#xff0c;并没有统一的、标准的DDD工程架构。有些团队可能遵循经典的DDD四层架构&#xff0c;或改进的DDD四层架构&#xff0c;有些团队可能综合考虑分层架构、整洁架构、六边形架构等多种架构…

数据仓库选择Greenplum还是SQL-on-Hadoop

Greenplum和Hadoop都是为了解决大数据并行计算而出现的技术&#xff0c;二者的相似点在于&#xff1a; 分布式存储数据在多个节点上。采用分布式并行计算框架。支持向外扩展来提高整体的计算能力和存储容量。支持X86开放集群架构。 但两种技术在数据存储和计算方法上&#xf…

ADS-600树脂,除COD有机物树脂,大孔树脂型号,矿井水有机物

基于吸附功能的聚苯乙烯特种树脂 Tulsimer ADS-600 是一款没有离子官能基的&#xff0c;由交联聚苯乙烯合成的功能强大的吸附型树脂。 Tulsimer ADS-600 主要应用于水溶液中吸附酚及其化合物&#xff0c;氯代烃等含氯物质&#xff0c;表面活性剂&#xff0c;氨基酸&#…

Python系列模块之标准库OS详解

感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01; 目录 ​一、模块 1.1 模块的定义 1.2 模块的分类 1.3 模块的基本导入语法 二、Python中的包 三、标准库之os模块 实战&#xff1a; 钉钉告警应用 一、模块 1.1 模块的定义 Python 模块(Module)&a…

【JAVA程序设计】(C00139)基于Springboot+Thymeleaf的药店管理系统

基于SpringbootThymeleaf的药店管理系统 项目简介项目获取开发环境项目技术运行截图 项目简介 本项目为基于SpringbootThymeleaf的药店管理系统&#xff08;医院药品管理系统、医院管理系统&#xff09;&#xff0c;本项目主要功能为&#xff1a; 药品库存管理&#xff08;登记…