观察者模式、中介者模式和发布订阅模式
观察者模式
定义
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新
观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯
例如生活中,我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸
报社和订报纸的客户就形成了一对多的依赖关系
被观察者知道观察者的存在,同时管理所有的观察者
实现
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 = houseOwnerthis.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就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)
相关文章:

观察者模式、中介者模式和发布订阅模式
观察者模式 定义 观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新 观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式…...

PHP-Mysql图书管理系统--【白嫖项目】
强撸项目系列总目录在000集 PHP要怎么学–【思维导图知识范围】 文章目录 本系列校训本项目使用技术 首页phpStudy 设置导数据库后台的管理界面数据库表结构项目目录如图:代码部分:主页的head 配套资源作业: 本系列校训 用免费公开视频&am…...

网络传输层协议:UDP和TCP
背景知识 再谈端口号 端口号(Port)标识了一个主机上进行通信的不同的应用程序; 在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信(可以通过 netstat -…...

ElementUI Select选择器如何根据value值显示对应的label
修改前效果如图所示,数据值状态应显示为可用,但实际上仅显示了状态码1,并没有显示其对应的状态信息。在排查了数据类型对应关系问题后,并没有产生实质性影响,只好对代码进行了如下修改。 修改前代码: <…...

Kotlin 内联函数语法之let、apply、also、run、with的用法与详解
一、介绍 kotlin的语法千奇百怪,今天我们将介绍项目中频率使用比较高的几个内联函数。 二、什么叫内联函数? 内联函数 的语义很简单:把函数体复制粘贴到函数调用处 。使用起来也毫无困难,用 inline关键字修饰函数即可。 语法&a…...

Swift 中如何判断是push 过来的页面 还是present过来的 页面
在 Swift 中,可以通过检查当前视图控制器的 presentingViewController 属性来判断是通过 push 过来的页面还是 present 过来的页面。 下面是一个示例代码,展示如何判断是通过 push 还是 present 过来的页面: if let presentingViewControll…...

基于K8s环境·使用ArgoCD部署Jenkins和静态Agent节点
今天是「DevOps云学堂」与你共同进步的第 47天 第⑦期DevOps实战训练营 7月15日已开营 实践环境升级基于K8s和ArgoCD 本文节选自第⑦期DevOps训练营 , 对于训练营的同学实践此文档依赖于基础环境配置文档, 运行K8s集群并配置NFS存储。实际上只要有个K8s集…...

874. 模拟行走机器人
874. 模拟行走机器人 机器人在一个无限大小的 XY 网格平面上行走,从点 (0, 0) 处开始出发,面向北方。该机器人可以接收以下三种类型的命令 commands : -2 :向左转 90 度-1 :向右转 90 度1 < x < 9 :…...

【Linux】- RPM 与 YUM
RPM 与 YUM 1.1 rpm 包的管理1.2 rpm 包的简单查询指令1.3 rpm 包的其它查询指令:1.4 卸载 rpm 包:2.1 安装 rpm 包3.1 yum3.2 yum 的基本指令3.3 安装指定的 yum 包3.4 yum 应用实例: 1.1 rpm 包的管理 介绍 rpm 用于互联网下载包的打包及安…...

Visual Studio 2015编译器 自动生成 XXX_EXPORTS宏
XXX_EXPORTS宏 XXX_EXPORTS宏是由Visual Studio 2015编译器自动生成的。这个宏用于标识当前项目是一个导出符号的动态链接库(DLL)项目。在使用Visual Studio 2015创建Win32项目时,编译器会自动添加这个宏到项目的预定义宏中。 这个宏的作用…...

HTML5的应用现状与发展前景
HTML5,作为Web技术的核心,已经深深地改变了我们看待和使用Web的方式。它不仅提供了数不尽的新特性和功能,还使得Web设计和开发更加互动、更加直观。这篇文章将探讨HTML5的当前应用现状,以及它的未来发展前景。 HTML5的应用现状 H…...

day44-Spring_AOP
0目录 1.2.3 1.Spring_AOP 实体类: Mapper接口: Service和实现类: 测试1: 运行后: 测试2:无此型号时 测试3:库存不足时 解决方案1:事务声明管理器 测试:…...

selenium IDE 接入jenkins-转载
Selenium-IDE脚本录制,selenium-side-runner自动化测试教程_51CTO博客_selenium ide录制脚本 备忘录...

云计算结合数据科学突破信息泛滥(下)
大家好,本文将继续讨论云计算结合数据科学突破信息泛滥的相关内容,讲述其余三个关键组成部分。 3.数据清理和预处理 收集数据并将其存储在云端之后,下一步是将数据进行转换。因为原始数据经常包含错误、不一致和缺失的值,这些都…...

蓝桥杯单片机第十二届国赛 真题+代码
iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求,进行代码调试和修改。 */ #include <STC1…...

MyBatis学习笔记之缓存
文章目录 一级缓存一级缓存失效 二级缓存二级缓存失效二级缓存相关配置 MyBatis集成EhCache 缓存:cache 缓存的作用:通过减少IO的方式,来提高程序的执行效率 mybatis的缓存:将select语句的查询结果放到缓存(内存&…...

小程序 WxValidate.js 再次封装
util.js // 合并验证规则和提示信息 const filterRules (objectItem) > {let rules {}, messages {};for (let key in objectItem) {rules[key] objectItem[key].rulesmessages[key] objectItem[key].message}return { rules, messages } }module.exports {filterRule…...

redis 第三章
目录 1.主从复制 2.哨兵 3.集群 4.总结 1.主从复制 结果: 2.哨兵 3.集群 4.总结 通过集群,redis 解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。...

MYSQL常见面试题汇总
MYSQL常见面试题汇总 1. 什么是MYSQL?它有哪些特点? MYSQL是一种开源的关系型数据库管理系统。它具有以下特点: 高性能:MYSQL能够处理大量的并发请求,并提供快速的响应时间。可靠性:MYSQL具有数据持久化…...

Java接口通过token登录实现页面跳转到登录成功后的页面
首先,你需要在接口请求中将token作为参数传递给后端,后端需要对token进行验证并获取登录用户的信息。 在验证通过后,你可以将登录成功后的页面链接返回给前端,前端通过跳转到该链接来实现页面跳转。 以下是一个简单的Java代码演…...

Linux-文件管理
1.文件管理概述 1.Bash Shell对文件进行管理 谈到Linux文件管理,首先我们需要了解的就是,我们要对文件做些什么事情? 其实无非就是对一个文件进行、创建、复制、移动、查看、编辑、压缩、查找、删除、等等 例如 : 当我们想修改系统的主机名…...

Android getevent用法详解
TP驱动调试分享——基于Qualcomm SDM710平台Android9.0,TP 采用I2C方式和CPU进行通信_高通tp驱动_永恒小青青的博客-CSDN博客 手机触摸屏扫描信号实测波形_触摸屏报点率_AirCity123的博客-CSDN博客 如何查看TP报点率?触摸TP查看详细信息 adb shell ge…...

面试题-TS(二):如何定义 TypeScript 中的变量和函数类型?
面试题-TS(二):如何定义 TypeScript 中的变量和函数类型? 一、 变量类型的定义 在TypeScript中,我们可以使用冒号(:)来指定变量的类型。以下是一些常见的变量类型: 布尔类型(boolean):表示tr…...

【4】-多个User执行测试
目录 一个locustfile中有多个User 使用--class-picker指定执行 小结 一个locustfile中有多个User from locust import task, HttpUserclass User01(HttpUser):weight 3 # 权重host https://www.baidu.comtaskdef user_01_task(self):self.client.get(url/, nameuser_01_…...

基于Eisvogel模板的Markdown导出PDF方法
Requirements 模板地址:Wandmalfarbe/pandoc-latex-template Pandoc:Pandoc官网 Latex环境:例如TexLive Pandoc参数 --template"模板存放位置" --listings --pdf-enginexelatex --highlight-style kate -V CJKmainfontSimSun -V C…...

linux服务器安装redis
一、安装下载 下载安装参考文章 下载安装包地址:https://download.redis.io/releases/ 亲测有效,但是启动的步骤有一些问题 安装完成!!! 二、启动 有三种启动方式 默认启动指定配置启动开机自启 说明:…...

QT中信号和槽本质
信号 信号的本质就是事件 在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。 槽 槽函数是一类特殊的功能的函数,也可以作为类的普通成员函数来使用 在Qt中槽函数的所有者也是某个类的实例对象。 信号和槽的关系 在Qt中我…...

layui各种事件无效(例如表格重载或 分页插件按钮失效)的解决方法
下图是我一个系统的操作日志,在分页插件右下角嵌入了一个导出所有数据的按钮 ,代码没有任何问题,点击导出按钮却失效 排查之后,发现表格标签table定义了ID又定义了lay-filter,因我使用的layui从2.7.6升级到2.8.11&…...

flutter开发实战-父子Widget组件调用方法
flutter开发实战-父子Widget组件调用方法 在最近开发中遇到了需要父组件调用子组件方法,子组件调用父组件的方法。这里记录一下方案。 一、使用GlobalKey 父组件使用globalKey.currentState调用子组件具体方法,子组件通过方法回调callback方法调用父组…...

策略模式的实现与应用:掌握灵活算法切换的技巧
文章目录 常用的设计模式有以下几种:一.创建型模式(Creational Patterns):二.结构型模式(Structural Patterns):三.行为型模式(Behavioral Patterns):四.并发…...