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

news2025/5/22 17:12:15

观察者模式

定义

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

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

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

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

实现

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就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)

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

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

相关文章

【洛谷算法题】B2025-输出字符菱形【入门1顺序结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】B2025-输出字符菱形【入门1顺序结构】&#x1f30f;题目描述&#x1f30f;输入格…

1 第一个vue程序

复习次数 &#xff1a;✔ 1.1 vue优势 1.2 vue环境 直接在idea的插件搜vue.js&#xff0c;然后下载。 接着创建一个空项目&#xff0c;并添加模块。然后&#xff0c;创建一个html文件。 1.3 vue例子 完整的html代码如下&#xff1a; <!DOCTYPE html> <html lang&qu…

Linux Day01

目录 一、Linux终端介绍 二、Linux目录介绍 1.目录结构 2.常见目录说明 3.绝对路径与相对路径 4.家目录 一、Linux终端介绍 二、Linux目录介绍 Linux目录&#xff1a;是从根目录"/"开始的 是一棵倒着的树 1.目录结构 2.常见目录说明 目前记住 bin 存放常用命…

网络安全领域关键信息泄露事件引发关注

近日&#xff0c;一家知名网络安全公司发布了一份报告揭露了一起重大信息泄露事件。据称&#xff0c;该事件涉及大量敏感用户数据的泄露引发了全球网络安全领域的广泛关注。 根据报道&#xff0c;该事件发生在全球范围内涉及多个国家和组织。专家指出&#xff0c;此次泄露事件…

ESP-C2模组实现透传示例说明

WIFI-TTL透传模块说明 V 1.0 2022-11-24 1 简介 WiFi-TTL透传模块基于我司DT-ESPC2-12模块研发&#xff0c;引出串口TTL、EN、STATE 等引脚。产品内置我司最新版本的串口透传固件可完成设备TTL 端口到WiFi/云的数据实时透传。本模块可直接取代原有的有线串口&#xff0c;实现…

java注解和自定义注解

目录 一、注解的概念 二、注解的类型 2.1、内置注解 2.2、元注解 2.2.1、各个元注解的作用 2.3、自定义注解 2.4、自定义注解实现及测试 一、注解的概念 1、注解的作用 ①&#xff1a;注解一般用于对程序的说明&#xff0c;就像注释一样&#xff0c;但是区别是注释是给…

出现了HTTPSConnectionPool(host=‘huggingface.co‘, port=443)错误的解决方法

在下载huggingface 模型的时候&#xff0c;经常会出现这个错误&#xff0c;HTTPSConnectionPool(host‘huggingface.co’, port443)&#xff0c;即使你已经有了正确的上网姿势。 如在下载Tokenizer的时候&#xff0c;就会出现&#xff1a; tokenizer AutoTokenizer.from_pre…

权智A133P 安卓10移植SPI转串WK2124驱动

硬件连接示意图 主控CPU通过SPI总线与WK2XXX芯相连接。WK2XXX控制4个UART的数据收发。 其中重要的参数有CS片选线和IRQ中断引脚。 LInux串口驱动框架 当WK2XXX驱动在内核注册成功后&#xff0c;会在/dev目录下面生成ttysWK0,ttysWK1,ttysWK2,ttysWK3节点。上层通过open,read,w…

pytest 第三方插件

目录 前言&#xff1a; 顺序执行&#xff1a;pytest-ordering 失败重试&#xff1a;pytest-rerunfailures 并行执行&#xff1a;pytest-xdist 前言&#xff1a; pytest 是一个广泛使用的 Python 测试框架。它具有强大的测试运行器、测试驱动开发和测试结果可视化等功能。除…

《面试1v1》如何能从Kafka得到准确的信息

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

对高校数字化转型的思考

数字新技术与国民经济各产业的融合深化&#xff0c;使行业产业数字化、网络化、全球化、知识化、智能化趋势愈发显著&#xff0c;深刻改变着人的职业生涯、现代社会对人才的需求和新型就业形式&#xff0c;引发教育资源、形态和范式的深刻变革。数字化转型对于提高学校管理效率…

Redis简介、常用命令

目录 一、​​关系数据库​​与非关系型数据库概述 1.1 关系型数据库 1.2 非关系型数据库 二、关系数据库与非关系型数据库区别 2.1 数据存储方式不同 2.2 扩展方式不同 2.3 对事务性的支持不同 三、非关系型数据库产生背景 四、Redis简介 4.1 Redis的单线程模式 4.…

Linux系列---【Ubuntu 20.04安装KVM】

Ubuntu 20.04安装KVM 一、安装kvm 1.安装kvm sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils 2. 将当前用户添加至libvirt 、 kvm组 sudo adduser $USER libvirt sudo adduser $USER kvm 3.验证安装 virsh list --all 4.启动libvert sudo syst…

Jmeter 压测实战:Jmeter 二次开发之自定义函数

目录 1 前言 2 开发准备 3 自定义函数核心实现 3.1 新建项目 3.2 继承实现 AbstractFunction 类 3.3 最终项目结构 4 Jmeter 加载扩展包 4.1 maven 构建配置 4.2 项目打包 4.3 Jmeter 加载扩展包 5 自定义函数调用调试 5.1 打开 Jmeter 函数助手&#xff0c;选择自…

0基础学习VR全景平台篇 第70篇:VR直播-如何设置付费观看、试看

对于拥有优质内容的VR直播&#xff0c;可以通过付费观看的方式进行内容变现&#xff0c;是当下非常流行的商业模式。 付费价格&#xff1e;0时便会自动弹出“试看时间”的设置项。试看时间&#xff1d;0秒时&#xff0c;用户进入直播间需要先付费才可观看&#xff1b;试看时间&…

【JVM】详解JVM的五大内存模型、可能出现的异常以及堆栈引用易错点

文章目录 1、堆(线程共享)2、方法区(线程共享)3、虚拟机栈&#xff08;线程私有&#xff09;4、本地方法栈(线程私有)5、程序计数器(线程私有)6、易错点 源自&#xff1a;深入理解Java虚拟机&#xff1a;JVM高级特性与最佳实践&#xff08;第3版&#xff09; 周志明 1、堆(线程…

Docker 镜像构建 搭建分布式LNMP论坛 实践

地址规划 nginx 172.18.0.10 mysql 172.18.0.20 php 172.18.0.30 宿主机准备 拉取镜像&#xff0c;下面以此镜像为基础 docker pull centos:7 创建自定义网段以便指定 IP 不变动 docker network create --subnet172.18.0.0/16 --opt "com.docker.network.bridge.na…

【计算机网络】计算机网络基础知识总结(秋招篇)

文章目录 前言计算机网络笔记TCP和UDP分别是什么 有什么区别基于TCP UDP这两个协议的上层协议有哪些&#xff1f;TCP和UDP分别在哪些领域被用的多&#xff1f;TCP实现可靠性传输用了哪些技术&#xff1f;&#xff08;TCP如何实现可靠性传输&#xff09;讲一下超时重传和超时定时…

T3/A40i支持Linux-5.10新内核啦,Docker、Qt、Python统统升级!

自2021年创龙科技推出全志国产化率100%的T3/A40i工业核心板后&#xff0c;不到两年时间已超过800家工业客户选择创龙科技T3/A40i平台。随着客户产品的不断升级与迭代&#xff0c;部分“能源电力”、“工业自动化”行业客户对T3/A40i的Linux版本提出了更高要求&#xff0c;主要涉…

Jmeter查看结果树之查看响应的13种详解方法

Jmeter查看结果树查看响应有哪几种方法&#xff0c;可通过左侧面板底部的下拉框选择: 01 Text 查看结果树中请求的默认格式为Text&#xff0c;显示取样器结果、请求、响应数据3个部分内容。 取样器结果&#xff1a; 默认Raw展示&#xff0c;可以切换为Parsed视图&#xff0c…