首页 > 其他分享 >设计模式:观察者模式/发布-订阅模式

设计模式:观察者模式/发布-订阅模式

时间:2023-09-01 19:25:41浏览次数:112  
标签:订阅 对象 观察者 模式 发布者 设计模式 type

设计模式

wiki中将设计模式分为四类,分别是:

  • 创建模式(creational patterns)
  • 结构模式(structural patterns)
  • 行为模式(behavioral patterns)
  • 并发模式(concurrency patterns)

观察者/发布订阅模式属于其中的行为模式。

实际情境

公众号订阅

说到发布订阅,拿微信公众号举例,就很好理解,有n个人订阅了某公众号,在该公众号发布了推文后,这n个人就会收到推文发布的消息。

状态更新

在实际编程过程中,有一种情况很多人应该碰到过,就是在页面上完成一些与接口的交互操作后,要更新页面的某些状态,但这个状态由于某些原因无法立即获得,如果要得到状态的更新结果,可以有两种选择,一种是重复请求接口以获取,可以是用户手动刷新页面或请求接口、或者交互结束后自动开启接口轮询;第二种是建立长连接,等待服务端推送结果。第二种方式,就可以算作是一种发布-订阅,订阅者是客户端,订阅内容是页面状态,发布者就是服务端。

为什么说观察者/发布订阅属于行为模式从上述例子就可以理解,原本的行为是客户端主动去获取内容,应用了此模式后,行为变成了客户端订阅(发起长连接请求)、由服务端来推送消息,行为方式发生了变化;并且我们也可以看出,这样做提高了应用的性能,客户端不必要再多次地去发起请求,减少了建立网络请求的消耗。

定义

wiki中对这两个模式的作用有着相同的描述:

Define a one-to-many dependency between objects where a state change in one object results in all its dependents being notified and updated automatically.

翻译一下:

定义了对象之间一对多的依赖关系,其中一个对象的状态变更,会使其所有依赖对象收到通知并自动更新。

可以看出,这两个模式处理的是存在一对多依赖关系的双方之间的交互行为。

虽然描述相同,但两者的实际实现还是存在区别,我们可以再进一步了解。

观察者模式

在这种模式下,一个名为主体的对象会维护其依赖对象(即观察者)的列表,并自动通知它们任何状态变化,通常是通过调用它们的方法。

该模式通常用于在事件驱动软件中实现分布式事件处理系统。在这类系统中,主体通常被称为“事件流”或“事件流源”,而观察者则被称为“事件汇”。

大多数现代编程语言都包含实现观察者模式组件的内置事件结构。虽然不是强制性的,大多数观察者实现都使用后台线程监听主体事件和内核提供的其他支持机制。

发布-订阅模式

在软件架构中,“发布-订阅”是一种消息传递模式,在这种模式下,发布者将消息分类,再由订阅者接收。这与典型的由发布者直接将消息传递给订阅者的消息传递模式形成鲜明对比。

同样,订阅者对一个或多个类别表示感兴趣,只是接收感兴趣的信息,但并不知道有哪些发布者(如果有的话)。

发布-订阅是消息队列模式的同类,通常是更大的面向消息的中间件系统的一部分。大多数消息传递系统的API中同时支持发布/订阅和消息队列模型;比如Java消息服务(JMS)。

该模式提供了更高的网络可扩展性和更动态的网络拓扑结构,但也因此降低了修改发布者和发布数据结构的灵活性。

由上述两个定义可知,在发布-订阅模式中,发布者和订阅者的耦合度更低,订阅者并不知道消息的发布者,在这种模式下,通常会存在一个第三方的订阅中心,订阅中心接收到发布者的消息,然后再将消息分发给订阅者;而在观察者模式中,观察者是通过在主体身上放置监听器从而直接观察主体,相当于是发布者(主体)直接将消息传递给订阅者(观察者),此时两者的耦合性更高,这种模式下,观察者需要实现统一的接口以供主体调用,而主体则需要维护一个观察者的集合。

解决的问题

观察者模式可以解决以下问题:

  • 应在对象之间定义一对多的依赖关系时,不使对象紧密耦合
  • 当一个对象更改状态时,应自动更新不限数量的依赖对象
  • 一个对象可以通知多个其他对象

通过定义一个直接更新依赖对象状态的对象(主体)来定义对象间的一对多依赖关系是不灵活的,因为它将主体与特定的依赖对象联系在一起。然而,从性能角度,或者对象的实现是紧密耦合的(比如每秒执行数千次的低级内核结构),这可能是适用的。在某些情况下,紧耦合对象可能难以实现,而且不容易复用,因为它们会引用并感知许多具有不同接口的对象。

做法描述

那么软件设计中的观察者模式具体是怎么做的呢?以下是wiki给出的描述:

  • Define Subject and Observer objects.
  • When a subject changes state, all registered observers are notified and updated automatically (and probably asynchronously).

The sole responsibility of a subject is to maintain a list of observers and to notify them of state changes by calling their update() operation. The responsibility of observers is to register and unregister themselves with a subject (in order to be notified of state changes) and to update their state (to synchronize their state with the subject's state) when they are notified. This makes subject and observers loosely coupled. Subject and observers have no explicit knowledge of each other. Observers can be added and removed independently at run time. This notification-registration interaction is also known as publish-subscribe.

翻译一下:

  • 定义主体观察者对象
  • 当主体改变状态,所有注册的观察者都会收到通知并自动更新(可能是异步的)。

主体的唯一职责是维护一个观察者列表,并通过调用观察者的update()操作来通知它们状态的变更。观察者的职责是向主体注册或取消注册(以便收到状态变更的通知),并在收到通知时更新自己的状态(使自己的状态与主体的状态同步)。这使得主体和观察者松散耦合。主体和观察者彼此互不了解。观察者可以在运行时被单独添加和移除。这种‘通知-注册’交互也被称为‘发布-订阅’。

两者对比

根据wiki给出的观察者模式对应的做法描述,可以看到观察者模式中的'通知-注册'也被称为‘发布-订阅’,但开发者需要明白,消息传递模式中的‘发布-订阅’,发布者和订阅者没有直接接触,而是增加了一个消息分发中心,这个消息分发中心可以类比为代理模式中的代理,通常需要维护消息队列。

这种‘发布-订阅’模式的实现相比于普通的观察者模式具有以下两个优势:

  1. 松耦合

    发布者和订阅者不需要知道对方的相关信息

  2. 可扩展性

    如果有需要,可以随时增加新的订阅者来订阅发布者的消息,而发布者不需要做修改,甚至不需要知道

应用场景

发布-订阅模式从字面上来看,主要的应用场景就在于消息(如状态)的传递。

事件监听

根据上述定义部分的内容,可以看出事件处理就是应用了观察者模式,比如说给按钮添加事件处理程序:

<button id="addButton">
  新增待办事项
</button>
let button = document.querySeletor('#addButton');
button.addEventListener('click', () => {
  console.log('待办事项+1');
});

对于上述代码我们可以这么理解,id为addButton的按钮,通过addEventListener在自己身上按了一个观察者,这个观察者有一个函数可用处事件处理,在按钮的交互状态发生改变时(从普通状态变为活动状态),就通知这个观察者更新状态(即调用这个事件处理程序)。

另外,用过vue源码的小伙伴应该都知道,vue中的响应式就应用了观察者模式,每个组件data中的数据可以看作是主体,然后这些数据的观察者可以是vue实例对象、computed属性、watcher等等,这些观察者会被维护在data中每个数据对应的deps数组中。

事件总线

事件总线可以看作是一个订阅中心。比如在Vue中我们可以使用EventBus(本质上也是Vue实例)来作为事件中心,以完成事件的调度分发。使用方法如下:

// 创建一个事件总线并导出
const EventBus = new Vue();
export default EventBus;

// 在主文件中引入EventBus,并挂载到全局
import bus from 'EventBus文件路径';
Vue.prototype.bus = bus;

// 订阅事件“someEvent”
// 这里func指someEvent这个事件的监听函数,也即在收到消息后通知订阅者的方法
this.bus.$on('someEvent', func);

// 发布(触发)事件“someEvent”
// 这里params指someEvent这个事件被触发时回调函数接收的入参,也就是传递给订阅者的状态
this.bus.$emit('someEvent', params);

实现

在JavaScript我们可以通过以下代码来实现一个观察者模式:

let Subject = {
    _state: 0,
    _observers: [],
    add: function(observer) {
        this._observers.push(observer);
    },
    getState: function() {
        return this._state;
    },
    setState: function(value) {
        this._state = value;
        for (let i = 0; i < this._observers.length; i++)
        {
            this._observers[i].signal(this);
        }
    }
};

let Observer = {
    signal: function(subject) {
        let currentValue = subject.getState();
        console.log(currentValue);
    }
}

Subject.add(Observer); // 主体管理自己的依赖对象
Subject.setState(10);
//Output in console.log - 10

发布-订阅模式也可以通过一个简单的EventEmitter来实现:

class EventEmitter {
  constructor() {
    this.events = {};
  }
  on(type, listern) {
    if (this.events[type]) {
      this.events[type].push(listener);
    } else {
      this.events[type] = [listener];
    }
  }
  emit(type, ...args) {
    if (this.events[type]) {
      this.events[type].forEach(fn => fn.call(this, ...args));
    }
  }
  once(type, listener) {
    const _ = this;
    function oneTime(...args) {
      listener.call(this, ...args);
      _.off(type, oneTime);
    }
    _.on(type, oneTime);
  }
  off(type, listener) {
    if (this.events[type]) {
      const index = this.events[type].indexOf(listener);
      this.events[type].splice(index, 1);
    }
  }
}

此处EventEmitter就是一个订阅中心。

标签:订阅,对象,观察者,模式,发布者,设计模式,type
From: https://www.cnblogs.com/beckyyyy/p/17672689.html

相关文章

  • 深入解析 Java 抽象工厂模式:创建跨平台图形绘制工具的设计与实现
    深入解析Java抽象工厂模式:创建跨平台图形绘制工具的设计与实现抽象工厂模式(AbstractFactoryPattern)是Java中一种常用的设计模式,它属于创建型模式的一种。该模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体实现类。在本文中,我们将详细介绍抽象工厂模式的概......
  • 设计模式-创建型-原型模式
    title:设计模式-创建型-原型模式keywords:设计模式cover:[https://s1.ax1x.com/2023/08/31/pP01Vit.png]#sticky:10banner:type:imgbgurl:https://s1.ax1x.com/2023/08/31/pP01Vit.pngbannerText:设计模式-创建型-原型模式categories:设计模式tags:-......
  • 设计模式-创建型-单例模式
    title:设计模式-创建型-单例模式keywords:设计模式cover:[https://s1.ax1x.com/2023/08/31/pP01Vit.png]#sticky:10banner:type:imgbgurl:https://s1.ax1x.com/2023/08/31/pP01Vit.pngbannerText:设计模式-创建型-单例模式categories:设计模式tags:-......
  • Scrum工作模式及敏捷工具
    ​Scrum工作模式是一种敏捷软件开发方法,其核心是团队合作和自我组织,旨在通过短周期的迭代开发,实现快速反馈和持续改进。Scrum工作模式包括以下角色和活动:1、产品负责人(ProductOwner):负责识别需求,确定产品范围、优先级和用户故事,并与开发团队保持密切沟通。敏捷需求管理示例:2......
  • KMP算法--解决字符串匹配问题--模式串是否在文本串出现过
    KMP算法--解决字符串匹配问题--模式串是否在文本串出现过*利用之前判断过的信息,通过next数组保存最长公共子序列的长度*搜索词/模式串移动的位数=已匹配的字符数-对应的部分匹配值在韩的例子里ABCDABD初次匹配匹配了ABCDAB6位,对应2,所以移动6-2=4位e.g.文本串aabaabaaf......
  • go-optioner:轻松生成函数选项模式代码
    [Go开源工具]go-optioner:轻松生成函数选项模式代码原创 陈明勇 Go技术干货 2023-07-2508:02 发表于广东收录于合集#Go开源1个大家好,我是 陈明勇,一个热爱技术,喜欢专研技术的程序员。Go技术干货专注于分享Go技术干货知识(基础、进阶、原理等)。50篇原创......
  • 限流规则-流控模式之关联模式
            ......
  • Day12_文件操作的x模式和b模式
    1.x模式: 2.b模式: 3.b模式应用案例与文件循环读取_方式一: 4.b模式应用案例与文件循环读取_方式二:5.文本文件copy工具,读取写入新地址: ......
  • 23种设计模式之代理模式
    代理设计模式(ProxyDesignPattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理......
  • Ftp基础(一):基于Vsftpd(主动模式和被动模式)安装Ftp
      如果是Windows下,我们一般使用IIS来部署一套Ftp,如果是Linux下,个人一般使用Vsftpd来搭建一套Ftp。  至于什么是Ftp,就不介绍了,本文就Ubuntu下安装配置Vsftpd,CentOS其实差不多,所以这里就当做笔记了  安装    Ubuntu下安装Vsftpd很简单,使用apt安装即可  #更新......