设计模式之-发布订阅者模式

发布订阅模式可以看作是观察者模式的一种升级版本,和观察者模式在结构上比较相似,但是它们在进行消息传递的时候,传递方式会有所不同。

  1. 观察者模式

直接通信:在观察者模式中,观察者(Observer)直接订阅主体(Subject)。当主体发生改变时,它直接通知这些观察者。

双向依赖:观察者需要知道主体,主体需要维护一个观察者列表

主体状态:观察者通常对主体的状态变化感兴趣,它们可能会从主体中检索状态。

实例:很多GUI工具使用观察者模式,例如,一个按钮(主体)可以有多个事件监听器(观察者)

  1. 发布订阅模式

间接通信:发布-订阅模式使用一个称作代理(Broker)或消息中介(Message Queue)的第三方组件,发布者(Publisher)不直接发送消息给订阅者(Subscriber),而是通过消息中介

解耦:发布者和订阅者不需要知道对方的存在。它们只需要知道相关的消息或事件类型。

异步通信:通常发布-订阅模式支持异步处理,订阅者可以在未来的任何时间点处理收到的消息

动态关系:订阅者可以根据需要随时订阅或取消订阅消息。

实例:消息队列(如 RabbitMQ)、流处理(如 Kafka)和许多现代应用框架使用发布-订阅模式

1.先来看一个简单的例子

javascript 复制代码
class EventBus{
    constructor(){
        this.events = {};
    }
    on(event,callback){
        this.events[event] = this.events[event] || [];
        this.events[event].push(callback);
    }
    emit(event,data){
        (this.events[event] || []).forEach(cb=>cb(data));
    }
}
// 使用
const bus = new EventBus();
bus.on('message',data=>{
    console.log('receive---',data)
})
bus.emit('message','hello,syt');

2.写一个ts版本的例子

下面这是一段伪代码,

typescript 复制代码
// request.ts文件
import axios,{ AxiosResponse } from 'axios';
import router from './router';
import { message } from 'ant-design-vue';

const ins = axios.create({
    baseURL:'http://localhost:3000',
});
const successHandler = (res:AxiosResponse):any=>{
    //略
}
const errorHandler = (error:any):any =>{
    if(error.response.status===401){
        //在失败的拦截器里面如果是401的,登陆过期,
        // 有一个提示弹窗
        message.error('登录失败,请重新登录');
        // 有一个路由跳转,逻辑是通的,但是这种写法不好,
        // 因为这个模块时在做网络请求的,网络的模块里面为啥会有组件库的依赖呢,网络里面为啥
        // 会对路由有依赖呢,这就很奇怪,这种一来就会造成耦合,就是网络跟我们的组件,界面耦合了,以及路由耦合了
        // 那么耦合会带来什么问题呢,耦合带来的问题一定是,你耦合了什么东西,那么你耦合的东西一变,你这里很有可能会跟着变化,
        // 这就会导致一个诡异的现象,将来界面需求变化了,你要去动网络
        // 比如界面的逻辑不仅仅要处理401还要处理400的逻辑,
        // 也有可能将来有一天登录失败不跳转到登录页面,而是弹出一个弹出层,在弹出层上登录,那这个问题就更加麻烦了
        // 那么这个耦合可能以后会越来越高,直到最后你会发现这个模块10%是在处理网络,90%是在处理路由和界面,那就完全没法看
        // 也就意味着你的工程将来很难维护
        router.push('/login');
    }
}

ins.interceptors.response.use(successHandler,errorHandler);

那么我们需要解藕,那么怎么做呢,那就是加中间层,我们先把他叫事件中心,一个模块发生了一件事,我不知道干嘛,比如发生了401了,你不知道干嘛也不要自作主张,这个事发生了,就抛出事件,让其别的模块监听事件,别的模块监听到事件就该干嘛干嘛,该是路由做的事情,就由路由去做,该是界面做的事就由界面去做,这样就解藕了,然后我们定义一个公共模块,即事件中心

typescript 复制代码
// eventEmitter.ts文件

const eventNames=['API:UN_AUTH','API:INVALID'];
type EventNames = (typeof eventNames)[number];


class EventEmitter {
    private listeners:Record<string,Set<Function>> = {
        'API:UN_AUTH':new Set(),
        'API:INVALID':new Set(),
    };
    on(eventName:EventNames,listener:Function){
        this.listeners[eventName].add(listener);
    }
    emit(eventName:EventNames,...args:any[]){
        this.listeners[eventName].forEach(listener=>listener(...args));
    }
}

export default new EventEmitter();

然后这个网络模块里面就不要咬和界面以及路由耦合到一起了,直接引入事件中心,通过它来发送消息

typescript 复制代码
// request.ts文件
import axios,{ AxiosResponse } from 'axios';
import emitter from './eventEmitter';
const ins = axios.create({
    baseURL:'http://localhost:3000',
});
const successHandler = (res:AxiosResponse):any=>{
    //略
}
const errorHandler = (error:any):any =>{
    if(error.response.status===401){
        emitter.emit('API:UN_AUTH')
    }else if(error.response.status===400){
        emitter.emit('API:INVALID')
    }
}

ins.interceptors.response.use(successHandler,errorHandler);

将来有一天路由要处理的话,那就导入事件中心,自己去处理自己该干的事,比如跳转路由,界面也是一样的,各自模块各自处理自己的事情

typescript 复制代码
//router.ts
import {createRouter,createWebHashHistory} from 'vue-router';
import emitter from './eventEmitter';

const router = createRouter({
    history:createWebHashHistory(),
    routes:[],
});
// 注册事件
emitter.on('API:UN_AUTH',()=>{
    router.push('/login')
});
export default router;

3.demo3

typescript 复制代码
// 发布者接口
interface IPublisher {
    // 发布者只管发布消息
    publish:(topic:string,message:string)=>void;
}
// 订阅者接口
// 现在是由订阅者自己来决定订阅哪个主题
interface ISubscriber {
    subscribe:(topic:string) => void;
    unsubscribe:(topic:string) => void;
    receive:(message:string) => void;
}
// 中间人接口
interface IBroker {
    // 订阅主题
    subscribe:(topic:string,subscriber:Subscriber)=>void;
    // 取消订阅
    unsubscribe:(topic:string,subscriber:Subscriber)=>void;
    // 发布消息
    publish:(topic:string,message:any)=>void;
}
// 中间人
// 回头发布者要发布消息,就会通知中间人,中间人再通知所有订阅者
// 并且订阅者的列表也是由中间人维护的

// 中间人要做的事情:1.帮发布者发布消息2.帮订阅者订阅主题消息
class Broker implements IBroker {
    // 内部维护一个主题和订阅者的列表
    private topics: Map<string, ISubscriber[]> = new Map();

    // 订阅方法
    subscribe(topic: string, subscriber: ISubscriber):void  {
        // 拿到对应的主题的观察者列表(订阅者列表)
        const subTopicSubscribers = this.topics.get(topic) || [];
        subTopicSubscribers.push(subscriber);
        this.topics.set(topic,subTopicSubscribers);

    };
    // 取消订阅方法
    unsubscribe(topic: string, subscriber: ISubscriber):void {
        const subTopicSubscribers = this.topics.get(topic)||[];
        const index = subTopicSubscribers.indexOf(subscriber);
        if(index!==-1){
            subTopicSubscribers.splice(index,1);
        }
    };
    // 发布消息方法
    publish (topic: string, message: any):void {
        // 获取到订阅了这个主题的订阅者列表
        const subTopicSubscribers = this.topics.get(topic) || [];
        // 遍历所有的订阅者,通知他们
        for(const subscriber of subTopicSubscribers){
            subscriber.receive(message);
        }
    };
    
}

//订阅者
class Subscriber implements ISubscriber {
    private id:number;
    private broker:IBroker;

    constructor(id:number,broker:IBroker){
        this.id = id;
        this.broker = broker;
    }
    subscribe(topic: string):void{
        // 订阅者要订阅哪一个主题,向中间人报名
        this.broker.subscribe(topic,this);
    };
    unsubscribe(topic: string):void {
        // 订阅者要取消订阅哪一个主题
        this.broker.unsubscribe(topic,this);

    };
    receive (message: string):void {
        console.log(`订阅者${this.id}接收到消息:${message}`);

    };
    
}
// 发布者
class Publisher implements IPublisher {
    private broker:IBroker;
    constructor(broker:IBroker){
        this.broker = broker;
    }
    // 发布消息的方法
    // 要发布具体的消息,我们只需要将消息交给中间人即可
    publish(topic:string,message:string):void {
        console.log(`发布者发了一个${topic}主题的消息:${message}`);
        // 通知中间人向指定主题发布消息
        this.broker.publish(topic,message);
    }
}

// 使用
// 创建一个中间人
const broker = new Broker();
// 创建一个发布者
const publisher = new Publisher(broker);

// 创建四个订阅者
const subscribe1 = new Subscriber(1,broker);
const subscribe2 = new Subscriber(2,broker);
const subscribe3 = new Subscriber(3,broker);
const subscribe4 = new Subscriber(4,broker);

// 四个订阅者订阅主题
subscribe1.subscribe('动作片');
subscribe2.subscribe('动作片');
subscribe3.subscribe('恐怖片');
subscribe4.subscribe('恐怖片');

publisher.publish("恐怖片","恐怖片上映了");

subscribe3.unsubscribe("恐怖片");

publisher.publish('恐怖片','咒怨终结版上映了')

非原创,来源渡一袁老师和谢杰老师,简单记录下吧

相关推荐
Yu_Lijing7 小时前
基于C++的《Head First设计模式》笔记——策略模式
c++·笔记·设计模式
sg_knight8 小时前
设计模式与代码重构
python·设计模式·重构·开发
好学且牛逼的马8 小时前
HttpServlet 深度拆解:从设计模式看透其核心原理
java·servlet·设计模式
__万波__1 天前
二十三种设计模式(十三)--模板方法模式
java·设计模式·模板方法模式
⑩-1 天前
Java设计模式-命令模式
java·设计模式·命令模式
AM越.1 天前
Java设计模式超详解--状态设计模式
java·开发语言·设计模式
FreeCode1 天前
智能体设计模式解析:ReAct模式
设计模式·langchain·agent
程序员爱钓鱼1 天前
BlackHole 2ch:macOS无杂音录屏与系统音频采集完整技术指南
前端·后端·设计模式
syt_10131 天前
设计模式之-观察者模式
观察者模式·设计模式