观察者模式、中介者模式和发布订阅模式

观察者模式

定义

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

观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯

例如生活中,我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸

报社和订报纸的客户就形成了一对多的依赖关系
在这里插入图片描述
被观察者知道观察者的存在,同时管理所有的观察者

实现

class Observer {
    update(params) {
        console.log(params)
    }
}

class Demo {
    update(params) {
        console.log(params)
    }
}

class ObserverList {
    constructor() {
        this.observerList = []
    }
    add(observer) {
        this.observerList.push(observer);
        return
    }
    delete(observer) {
        this.observerList = this.observerList.filter(ob => ob !== observer);
        return this;
    }
    get(index) {
        return this.observerList[index];
    }
    count() {
        return this.observerList.length;
    }
}

class Subject {
    observers = new ObserverList;
    add(observer) {
        this.observers.add(observer)
    }
    remove(observer) {
        this.observers.delete(observer);
    }
    notify(...params) {
        for (let i = 0; i < this.observers.count(); i++) {
            let item = this.observers.get(i)
            item.update(...params)
        }
    }
}

let sub = new Subject()
sub.add(new Observer)
sub.add(new Observer)
sub.add(new Demo)

sub.notify('测试观察者模式发出通知')

在这里插入图片描述

中介者模式

定义

在这里插入图片描述

在这个星型结构中,同事对象不再直接与其他的同事对象联系,通过中介者对象与另一个对象发生相互作用,中介者对象的存在保证了结构上的稳定,也就是说,系统的结构不会因为新对象的引入带来大量的修改工作。

如果一个系统中对象之间存在多对多的相互关系,可以将对象之间的一些交互行为从各个对象之间分离出来,并集中封装在一个中介者对象中,由中介者进行统一的协调,这样对象之间多对多的复杂关系就转变为相对简单的一对多关系,通过引入中介者来简化对象之间的复杂交互。

实现

以租房为例,租房者和房主都通过中介更新信息,中介将更新后的信息通知对应的对象

class Tenant {
    constructor(name, mediator) {
        this.name = name;
        this.mediator = mediator
    }

    contract(message) {
        this.mediator.contract(message, this)
    }

    getMessage(message) {
        console.log(message)
    }
}

class HouseOwner {
    constructor(name, mediator) {
        this.name = name;
        this.mediator = mediator
    }

    contract(message) {
        this.mediator.contract(message, this)
    }

    getMessage(message) {
        console.log(message)
    }
}

class Mediator {
    constructor(houseOwner, tenant) {
        this.houseOwner = houseOwner
        this.tenant = tenant
    }

    contract(message, person) {
        if (person == this.houseOwner) {
            this.tenant.getMessage(message)
        } else {
            this.houseOwner.getMessage(message)
        }
    }

    getTenant() {
        return this.tenant
    }
    setTenant(tenant) {
        this.tenant = tenant
    }
    getHouseOwner() {
        return this.houseOwner
    }
    setHouseOwner(houseOwner) {
        this.houseOwner = houseOwner
    }
}

let mediator = new Mediator()

let tenant = new Tenant('tenant',mediator)
let houseOwner = new HouseOwner('houseOwner',mediator)
mediator.setTenant(tenant)
mediator.setHouseOwner(houseOwner)


tenant.contract('你好房东 我是租客')
houseOwner.contract('你好租客 我是房东')

优点

  • 简化交互:中介者模式简化了对象之间的交互,它用中介者和租客房东的一对多交互代替了原来租客房东的多对多交互,一对多容易理解和扩展,将原本难以理解的网状结构转换为星型结构
  • 解耦租客房东对象:中介者模式可将各个租客房东对象解耦,有利于租客房东之间的松耦合,可以独立改变和复用每一个租客房东和中介者,增加新的中介者和新的租客房东类都很方便,更好地符合开闭原则
  • 减少租客房东子类个数:中介者将原本分布于多个对象间的行为集中起来,改变这些行为只需要生成新的中介者子类即可,这使得各个租客房东类可以被重用,无须对租客房东类进行扩展

缺点

中介者类复杂:由于具体中介者中包含了大量的同事之间的交互细节,可能会导致具体中介者类变得非常复杂,使得系统难以维护

发布订阅模式

发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在

同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在

class PubSub {
  constructor() {
    this.messages = {};
    this.listeners = {};
  }
  // 添加发布者
  publish(type, content) {
    const existContent = this.messages[type];
    if (!existContent) {
      this.messages[type] = [];
    }
    this.messages[type].push(content);
  }
  // 添加订阅者
  subscribe(type, cb) {
    const existListener = this.listeners[type];
    if (!existListener) {
      this.listeners[type] = [];
    }
    this.listeners[type].push(cb);
  }
  // 通知
  notify(type) {
    const messages = this.messages[type];
    const subscribers = this.listeners[type] || [];
    subscribers.forEach((cb, index) => cb(messages[index]));
  }
}

class Publisher {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  publish(type, content) {
    this.context.publish(type, content);
  }
}

class Subscriber {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  subscribe(type, cb) {
    this.context.subscribe(type, cb);
  }
}

const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';

const pubsub = new PubSub();

const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');

const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {
  console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {
  console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {
  console.log('subscriberC received', res)
});

pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);

灵感来源于:addEventListener DOM2事件绑定

  • 给当前元素的某一个事件行为,绑定多个不同的方法「事件池机制」
  • 事件行为触发,会依次通知事件池中的方法执行
  • 支持内置事件{标准事件,例如:click、dblclick、mouseenter…}

应用场景:凡是某个阶段到达的时候,需要执行很多方法「更多时候,到底执行多少个方法不确定,需要编写业务边处理的」,我们都可以基于发布订阅设计模式来管理代码;创建事件池->发布计划 向事件池中加入方法->向计划表中订阅任务 fire->通知计划表中的任务执行

let sub = (function () {
    let pond = {};

    // 向事件池中追加指定自定义事件类型的方法
    const on = function on(type, func) {
        // 每一次增加的时候,验证当前类型在事件池中是否已经存在
        !Array.isArray(pond[type]) ? pond[type] = [] : null;
        let arr = pond[type];
        if (arr.includes(func)) return;
        arr.push(func);
    };

    // 从事件池中移除指定自定义事件类型的方法
    const off = function off(type, func) {
        let arr = pond[type],
            i = 0,
            item = null;
        if (!Array.isArray(arr)) throw new TypeError(`${type} 自定义事件在事件池中并不存在!`);
        for (; i < arr.length; i++) {
            item = arr[i];
            if (item === func) {
                // 移除掉
                // arr.splice(i, 1); //这样导致数据塌陷
                arr[i] = null; //这样只是让集合中当前项值变为null,但是集合的机构是不发生改变的「索引不变」;下一次执行emit的时候,遇到当前项是null,我们再去把其移除掉即可;
                break;
            }
        }
    };

    // 通知事件池中指定自定义事件类型的方法执行
    const emit = function emit(type, ...params) {
        let arr = pond[type],
            i = 0,
            item = null;
        if (!Array.isArray(arr)) throw new TypeError(`${type} 自定义事件在事件池中并不存在!`);
        for (; i < arr.length; i++) {
            item = arr[i];
            if (typeof item === "function") {
                item(...params);
                continue;
            }
            //不是函数的值都移除掉即可,自己控制i的值
            arr.splice(i, 1);
            i--;
        }
    };

    return {
        on,
        off,
        emit
    };
})();

const fn1 = () => console.log(1);
const fn2 = () => console.log(2);
const fn3 = () => {
    console.log(3);
    sub.off('A', fn1);
    sub.off('A', fn2);
};
const fn4 = () => console.log(4);
const fn5 = () => console.log(5);
const fn6 = () => console.log(6);

sub.on('A', fn1);
sub.on('A', fn2);
sub.on('A', fn3);
sub.on('A', fn4);
sub.on('A', fn5);
sub.on('A', fn6);
setTimeout(() => {
    sub.emit('A');
}, 1000);

setTimeout(() => {
    sub.emit('A');
}, 2000);

观察者模式和发布订阅模式的区别

  • 观察者模式:某公司给自己员工发月饼发粽子,是由公司的行政部门发送的,这件事不适合交给第三方,原因是“公司”和“员工”是一个整体

  • 发布-订阅模式:某公司要给其他人发各种快递,因为“公司”和“其他人”是独立的,其唯一的桥梁是“快递”,所以这件事适合交给第三方快递公司解决
    上述过程中,如果公司自己去管理快递的配送,那公司就会变成一个快递公司,业务繁杂难以管理,影响公司自身的主营业务,因此使用何种模式需要考虑什么情况两者是需要耦合的

  • 在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)

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

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

相关文章

iOS--通知、代理、单例模式总结

通知 概要 观察者和被观察者都无需知晓对方&#xff0c;只需要通过标记在NSNotificationCenter中找到监听该通知所对应的类&#xff0c;从而调用该类的方法。并且在NSNotificationCenter中&#xff0c;观察者可以只订阅某一特定的通知&#xff0c;并对齐做出相应操作&#xf…

Ros终端出现找不到bash: /home/***/devel/setup.bash: 没有那个文件或目录

现象&#xff1a;Ros终端出现找不到bash: /home/***/devel/setup.bash: 没有那个文件或目录 问题&#xff1a;配置时路径写错 解决方法&#xff1a;改正路径 1.打开文件 gedit ~/.bashrc2.修改正确路径

解决分类任务中数据倾斜问题

大家好&#xff0c;在处理文本分类任务时&#xff0c;基准测试流行的自然语言处理架构的性能是建立对可用选项的理解的重要步骤。在这里&#xff0c;本文将深入探讨与分类相关的最常见的挑战之一——数据倾斜。如果你曾经将机器学习&#xff08;ML&#xff09;应用于真实世界的…

IDEA 使用 maven 搭建 spring mvc

1. 创建项目 1.1 创建成功之后配置 Spring MVC 1.2 勾选 Spring MVC 2.更改配置文件 2.1 更改web.xml配置 更改为 <servlet-mapping><servlet-name>dispatcher</servlet-name><url-pattern>/</url-pattern></servlet-mapping>2.2 dispat…

数据结构基础:3.单链表的实现。

单链表的介绍和实现 一.基本概念1.基本结构2.结构体节点的定义&#xff1a; 二.功能接口的实现0.第一个节点&#xff1a;plist1打印链表2创建一个节点3.头插4.头删5.尾插6.尾删7.查找8.在pos之前插入x9.在pos之后插入x10.删除pos位置11.删除pos的后一个位置12.链表释放 三.整体…

leetcode743. 网络延迟时间 floyd

https://leetcode.cn/problems/network-delay-time/ 有 n 个网络节点&#xff0c;标记为 1 到 n。 给你一个列表 times&#xff0c;表示信号经过 有向 边的传递时间。 times[i] (ui, vi, wi)&#xff0c;其中 ui 是源节点&#xff0c;vi 是目标节点&#xff0c; wi 是一个信…

SpringBoot使用PropertiesLauncher加载外部jar包

启用SpringBoot的PropertiesLauncher 使用SpringBoot的PropertiesLauncher可以优先加载外部的jar文件, 这样可以在程序运行前替换jar包, 官方文档: Launching Executable Jars 使用演示 建立一个SpringBoot工程, 工程中依赖一个叫自定义的utils包, 版本是1.0.0, 通过http接口…

LiveGBS流媒体平台GB/T28181功能-支持轮巡播放分屏轮巡值守播放监控视频轮播大屏轮询播放

LiveGBS支持轮巡播放分屏轮巡值守播放监控视频轮播大屏轮询播放 1、背景2、分屏展示3、选择轮播通道4、配置轮播间隔(秒)5、点击开始轮播6、轮播停止及全屏7、搭建GB28181视频直播平台 1、背景 视频监控项目使用过程中&#xff0c;有时需要大屏值守&#xff0c;值守的时候多分…

Sestra 实用教程(二)方程求解器

目 录 一、前言二、超单元分析三、惯性释放四、模态叠加法4.1 Eigenvalue solvers4.2 Static back substitution 五、模态综合法六、Master-Slave七、参考文献 一、前言 SESAM &#xff08;Super Element Structure Analysis Module&#xff09;是由挪威船级社&#xff08;DNV-…

mac m1安装Centos9

先看结果&#xff08;在mac M1 安装centos8 安装不成功的原因大部分是没有找到正确的系统&#xff09; 由于Cnetos8 停服&#xff0c;现有mac m1 上能够按照的Centos8 并非由官方发布&#xff0c;因此寻找官方发布的能够在mac m1上安装的centos版本。 在YouTuBe上找到一个视频…

redis突然变慢问题定位

CPU 相关&#xff1a;使用复杂度过高命令、数据的持久化&#xff0c;都与耗费过多的 CPU 资源有关 内存相关&#xff1a;bigkey 内存的申请和释放、数据过期、数据淘汰、碎片整理、内存大页、内存写时复制都与内存息息相关 磁盘相关&#xff1a;数据持久化、AOF 刷盘策略&…

IntelliJ IDEA 2023.2 最新变化

主要更新 AI Assistant 限定访问 Ultimate 在此版本中&#xff0c;我们为 IntelliJ IDEA 引入了一项重要补充 – AI Assistant。 AI Assistant 当前具备一组由 AI 提供支持的初始功能&#xff0c;提供集成式 AI 聊天&#xff0c;可以完成一些任务&#xff0c;例如自动编写文档…

[JAVAee]定时器

目录 定时器的含义 定时器的使用 定时器的解析 ①TaskQueue ​②TimerThread ③Timer 定时器的模拟实现 ①创建Task自定义类型 ②创建TimerThread类 ③Timer类 完整代码 定时器的含义 从名字上看,就是我们通俗理解的那个定时器.设置一定的时间,并在一定的时间后发生…

01 矩阵(力扣)多源广度优先搜索 JAVA

给定一个由 0 和 1 组成的矩阵 mat &#xff0c;请输出一个大小相同的矩阵&#xff0c;其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。 两个相邻元素间的距离为 1 。 输入&#xff1a;mat [[0,0,0],[0,1,0],[0,0,0]] 输出&#xff1a;[[0,0,0],[0,1,0],[0,0,0]] 输入…

代理模式--静态代理和动态代理

1.代理模式 定义&#xff1a;代理模式就是代替对象具备真实对象的功能&#xff0c;并代替真实对象完成相应的操作并且在不改变真实对象源代码的情况下扩展其功能&#xff0c;在某些情况下&#xff0c;⼀个对象不适合或者不能直接引⽤另⼀个对象&#xff0c;⽽代理对象可以在客户…

CentOS 8 错误: Error setting up base repository

配置ip、掩码、网关、DNS VMware网关可通过如下查看 打开网络连接 配置镜像的地址 vault.centos.org/8.5.2111/BaseOS/x86_64/os/

python 面向对象编程的特点 - 封装 - 继承(经典类、新式类) - 多态 - 静态方法、类方法 - 下划线的使用 - 回合制攻击游戏实验

目录 面向对象编程的特点&#xff1a; 封装&#xff1a;封装是将数据和操作&#xff08;方法&#xff09;封装在一个对象中的能力 继承&#xff1a;继承是指一个类&#xff08;子类&#xff09;可以继承另一个类&#xff08;父类&#xff09;的属性和方法。 我们为什么需要继…

PostgreSQL构建时间

– PostgreSQL构建时间 select make_timestamp(2023,7,27,7,34,16);

Ubuntu—vi编辑器的使用一

vi编辑器 vi是Linux中最基本的编辑器。但vi编辑器在系统管理、服务器配置工作中永远都是无可替代的。 vi编辑器的使用 vi有以下三种模式 命令行模式 用户在用vi编辑文件时&#xff0c; 最初进入的是该模式。可以进行复制、粘贴等操作 插入模式 进行文件编辑&#xff0c; 按…

Java的第十五篇文章——网络编程(后期再学一遍)

目录 学习目的 1. 对象的序列化 1.1 ObjectOutputStream 对象的序列化 1.2 ObjectInputStream 对象的反序列化 2. 软件结构 2.1 网络通信协议 2.1.1 TCP/IP协议参考模型 2.1.2 TCP与UDP协议 2.2 网络编程三要素 2.3 端口号 3. InetAddress类 4. Socket 5. TCP网络…