我想学会设计模式之发布订阅模式| 8月更文挑战

194 阅读3分钟

设计模式手册之订阅-发布模式

image-20210802222614229

官方定义:

订阅-发布模式:定义了对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都可以得到通知。

我的想法:

将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相应对象间的一致性,这样会给维护、扩展和重用都带来不便。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时,就可以使用订阅发布模式了。一个抽象模型有两个方面,其中一方面依赖于另一方面,这时订阅发布模式可以将这两者封装在独立的对象中,使它们各自独立地改变和复用。订阅发布模式所做的工作其实就是在解耦合。让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。


什么是“订阅-发布模式”?

订阅-发布模式:定义了对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都可以得到通知。

了解过事件机制或者函数式编程的朋友,应该会体会到“订阅-发布模式”所带来的“时间解耦”和“空间解耦”的优点。借助函数式编程中闭包和回调的概念,可以很优雅地实现这种设计模式。


“订阅-发布模式” vs 观察者模式

订阅-发布模式和观察者模式概念相似,但在订阅-发布模式中,订阅者和发布者之间多了一层中间件:一个被抽象出来的信息调度中心。

但其实没有必要太深究 2 者区别,因为《Head First 设计模式》这本经典书都写了:发布+订阅=观察者模式其核心思想是状态改变和发布通知。 在此基础上,根据语言特性,进行实现即可。

两者区别
  • 在发布者/订阅者模式中,组件与观察者模式完全分离。在观察者模式中,主题和观察者松散耦合。
  • 观察者模式主要是以同步方式实现的,即当发生某些事件时,主题调用其所有观察者的适当方法。发布服务器/订阅服务器模式主要以异步方式实现(使用消息队列)。
  • 发布者/订阅者模式更像是一种跨应用程序模式。发布服务器和订阅服务器可以驻留在两个不同的应用程序中。它们中的每一个都通过消息代理或消息队列进行通信。

代码实现

JS 中一般用事件模型来代替传统的发布-订阅模式。任何一个对象的原型链被指向Event的时候,这个对象便可以绑定自定义事件和对应的回调函数。

const Event = {
    clientList: {},
    // 绑定事件监听
    listen(key, fn) {
        if (!this.clientList[key]) {
            this.clientList[key] = [];
        }
        this.clientList[key].push(fn);
        return true;
    },
    // 触发对应事件
    trigger() {
        const key = Array.prototype.shift.apply(arguments),
            fns = this.clientList[key];
        if (!fns || fns.length === 0) {
            return false;
        }
        for (let fn of fns) {
            fn.apply(null, arguments);
        }
        return true;
    },
    // 移除相关事件
    remove(key, fn) {
        let fns = this.clientList[key];
        // 如果之前没有绑定事件
        // 或者没有指明要移除的事件
        // 直接返回
        if (!fns || !fn) {
            return false;
        }
        // 反向遍历移除置指定事件函数
        for (let l = fns.length - 1; l >= 0; l--) {
            let _fn = fns[l];
            if (_fn === fn) {
                fns.splice(l, 1);
            }
        }
        return true;
    }
};
// 为对象动态安装 发布-订阅 功能
const installEvent = obj => {
    for (let key in Event) {
        obj[key] = Event[key];
    }
};
let salesOffices = {};
installEvent(salesOffices);
// 绑定自定义事件和回调函数
salesOffices.listen(
    "event01",
    (fn1 = price => {
        console.log("Price is", price, "at event01");
    })
);
salesOffices.listen(
    "event02",
    (fn2 = price => {
        console.log("Price is", price, "at event02");
    })
);
salesOffices.trigger("event01", 1000);
salesOffices.trigger("event02", 2000);
salesOffices.remove("event01", fn1);
// 输出: false
// 说明删除成功
console.log(salesOffices.trigger("event01", 1000));