一文带你彻底搞懂发布与订阅设计

简介: 我们常说的发布订阅设计模式,也叫观察者模式,也就是事件监听机制,观察者模式订阅了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当这个主题对象发生改变时,会通知所有的观察者对象,使他们能够自动的更新自己!

23.jpg

一、介绍

我们常说的发布订阅设计模式,也叫观察者模式,也就是事件监听机制,观察者模式订阅了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当这个主题对象发生改变时,会通知所有的观察者对象,使他们能够自动的更新自己!

一个软件系统要求某个对象在发生变化时,某些其他的对象作出相应的改变,能做到这点的设计方案有很多,但观察者模式是满足这一要求的各种设计方案中最重要的一种。

从整体来看,观察者模式所涉及的角色有:

  • 抽象主题角色:抽象主题角色把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象;
  • 具体主题角色:将有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过的观察者发出通知;
  • 抽象观察者角色:为所有的具体观察者提供一个接口,在得到主题通知时更新自己;
  • 具体观察者角色:存储与主题的状态相关的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态协调

废话也不多说了,下面我们直接讲案例!

二、示例

抽象主题角色,有增加观察者、删除观察者、通知观察者的功能,内容如下:

public abstract class AbstractSubject {
    /** 用来保存注册的观察者对象 */
    private List<Observer> list = new ArrayList<Observer>();
    /**
     * 注册观察者对象
     */
    public void add(Observer observer){
        list.add(observer);
        System.out.println("add an observer");
    }
    /**
     * 删除观察者对象
     * @param observer
     */
    public void remove(Observer observer){
        list.remove(observer);
        System.out.println("delete an observer");
    }
    /**
     * 通知所有注册的观察者对象
     * @param state
     */
    public void notifyObservers(String state){
        for (int i = 0; i < list.size(); i++) {
            list.get(i).update(state);
        }
    }
}

具体主题角色,这个change方法放在子类中是因为可能不同的主题在改变观察者状态的时候会做一些不同的操作,因此就不统一放在父类Subject里面了,内容如下:

public class ConcreteSubject extends AbstractSubject{
    private String state;
    public void change(String newState){
        state = newState;
        System.out.println("主题状态:" + state);
        //状态发生改变,通知所有的观察者
        super.notifyObservers(state);
    }
}

观察者接口,内容如下:

public interface Observer {
    /**
     * 修改状态
     * @param state
     */
    void update(String state);
}

具体观察者实现了观察者接口,内容如下:

public class ConcreteObserver implements Observer{
    @Override
    public void update(String state) {
        System.out.println("观察者,收到状态:" + state);
    }
}

客户端调用代码,一旦主题调用了change方法改变观察者的状态,那么观察者Observer里面的observerState全都改变了,内容如下:

public class ObserverClient {
    public static void main(String[] args) {
        //创建一个主题角色
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        ConcreteObserver observer = new ConcreteObserver();
        //将观察者加入主题对象上
        subject.add(observer);
        //改变主题状态
        subject.change("hello world");
    }
}

运行结果如下:

add an observer
主题状态:hello world
观察者,收到状态:hello world

这里只添加了一个观察者,有兴趣的可以试试看多添加几个观察者,效果都是一样的,主题角色改变状态,观察者状态全变。

三、应用

观察者模式的两种模型

  • 推模型:主题对象向观察者推送主题的详细信息,不管观察者是否需要。推送的信息通常是主题对象的全部或部分数据,上面的例子就是典型的推模型。
  • 拉模型:主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中去获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样观察者在需要获取数据的时候,就可以通过这个引用来获取了。

两种模型的比较

  • 1、推模型是假设主题对象知道观察者需要的数据,拉模型是假设主题对象不知道观察者需要什么数据,干脆把自身传递过去,让观察者自己按需要取值。
  • 2、推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾到没有考虑到的使用情况,这意味着出现新的情况时,可能要提供新的update()方法。

JDK是有直接支持观察者模式的,就是java.util.Observer这个接口,内容如下:

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

这就是观察者的接口,定义的观察者只需要实现这个接口就可以了。update()方法,被观察者对象的状态发生变化时,被观察者的notifyObservers()方法就会调用这个方法,内容如下:

public class Observable {
    private boolean changed = false;
    private Vector obs;
    /** Construct an Observable with zero Observers. */
    public Observable() {
    obs = new Vector();
    }
    /**
     * Adds an observer to the set of observers for this object, provided 
     * that it is not the same as some observer already in the set. 
     * The order in which notifications will be delivered to multiple 
     * observers is not specified. See the class comment.
     *
     * @param   o   an observer to be added.
     * @throws NullPointerException   if the parameter o is null.
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }
    ...
}

这是被观察者的父类,也就是主题对象。这是一个线程安全的类,是基于Vector实现的。

创建一个观察者,内容如下:

import java.util.Observable;
import java.util.Observer;
public class Watched implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("观察者,收到状态:" + arg);
    }
}

创建一个主题,内容如下:

import java.util.Observable;
public class Subject extends Observable {
    private String data;
    public void setData(String newData){
        System.out.println("主题状态:" + newData);
        data = newData;
        setChanged();
        notifyObservers(data);
    }
}

写一个main函数调用,内容如下:

public class WatchedClient {
    public static void main(String[] args) {
        //创建观察者对象
        Watched watched = new Watched();
        //创建主题
        Subject subject = new Subject();
        //将观察者对象加入主题
        subject.addObserver(watched);
        //修改主题状态
        subject.setData("hello world");
    }
}

运行结果,内容如下:

主题状态:hello world
观察者,收到状态:hello world

看到主题对象改变的时候,观察者对象的状态也随之改变。

四、总结

引入设计模式最主要的作用我认为就是两点:

  • 去重复代码,使得代码更清晰、更易读、更易扩展
  • 解耦,使得代码可维护性更好,修改代码的时候可以尽量少改地方

使用观察者模式可以很好地做到这两点。增加观察者,直接new出观察者并注册到主题对象之后就完事了,删除观察者,主题对象调用方法删除一下就好了,其余都不用管。主题对象状态改变,内部会自动帮我们通知每一个观察者,是不是很方便呢?

观察者模式主要应用场景有:

  • 对一个对象状态的更新需要其他对象同步更新
  • 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节,如消息推送

五、参考

博客园 - 五月的仓颉 - 观察者模式


相关文章
|
消息中间件 存储 Cloud Native
揭秘发布订阅模式:让消息传递更高效
揭秘发布订阅模式:让消息传递更高效
揭秘发布订阅模式:让消息传递更高效
|
存储 Java 编译器
Go函数解密:底层工作原理
Go函数解密:底层工作原理
259 0
|
设计模式 前端开发 JavaScript
观察者模式 vs 发布-订阅模式:两种设计模式的对决!
欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚开始学习前端的读者们打造的。无论你是初学者还是有一些基础的开发者,我们都会在这里为你提供一个系统而又亲切的学习平台。我们以问答形式更新,为大家呈现精选的前端知识点和最佳实践。通过深入浅出的解释概念,并提供实际案例和练习,让你逐步建立起一个扎实的基础。无论是HTML、CSS、JavaScript还是最新的前端框架和工具,我们都将为你提供丰富的内容和实用技巧,帮助你更好地理解并运用前端开发中的各种技术。
|
消息中间件 Java 中间件
秒懂消息队列MQ,万字总结带你全面了解消息队列MQ
消息队列是大型分布式系统不可缺少的中间件,也是高并发系统的基石中间件,所以掌握好消息队列MQ就变得极其重要。接下来我就将从零开始介绍什么是消息队列?消息队列的应用场景?如何进行选型?如何在Spring Boot项目中整合集成消息队列。
24067 10
秒懂消息队列MQ,万字总结带你全面了解消息队列MQ
|
3月前
|
监控 架构师 NoSQL
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
spring 状态机 的使用 + 原理 + 源码学习 (图解+秒懂+史上最全)
|
5月前
|
存储 SQL 关系型数据库
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
京东面试:mysql深度分页 严重影响性能?根本原因是什么?如何优化?
|
6月前
|
存储 Java API
Java Optional 完全指南:彻底告别 NullPointerException
Java 8 引入的 `Optional` 类旨在解决 `null` 带来的空指针异常问题,通过提供容器类显式处理可能为空的值,提升代码健壮性和可读性。本文从基础到进阶解析 `Optional` 的用法,涵盖创建、检查、获取值、处理值等核心功能,结合实际应用场景与最佳实践,助你彻底告别 `NullPointerException`,编写更优雅的 Java 代码。
306 0
|
11月前
|
存储 Go 开发者
Go语言中的并发编程与通道(Channel)的深度探索
本文旨在深入探讨Go语言中并发编程的核心概念和实践,特别是通道(Channel)的使用。通过分析Goroutines和Channels的基本工作原理,我们将了解如何在Go语言中高效地实现并行任务处理。本文不仅介绍了基础语法和用法,还深入讨论了高级特性如缓冲通道、选择性接收以及超时控制等,旨在为读者提供一个全面的并发编程视角。
224 50
|
10月前
|
设计模式 消息中间件 供应链
前端必须掌握的设计模式——发布订阅模式
发布订阅模式(Publish-Subscribe Pattern)是一种设计模式,类似于观察者模式,但通过引入第三方中介实现发布者和订阅者的解耦。发布者不再直接通知订阅者,而是将消息发送给中介,由中介负责分发给订阅者。这种方式提高了异步支持和安全性,适合复杂、高并发场景,如消息队列和流处理系统。代码实现中,通过定义发布者、订阅者和中介接口,确保消息的正确传递。此模式在前端开发中广泛应用,例如Vue的数据双向绑定。
|
C++ Windows
vs2019 This application failed to start because it could not find or load the QT platform plugin
这篇文章介绍了在VS2019中解决QT程序运行时出现的“无法找到或加载QT平台插件”错误的步骤,通过将必要的DLL文件和插件目录复制到项目解决方案中解决了问题。