首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TS 设计模式08 - 发布订阅模式

TS 设计模式08 - 发布订阅模式

作者头像
love丁酥酥
发布2020-09-19 13:55:28
1K0
发布2020-09-19 13:55:28
举报
文章被收录于专栏:coding for lovecoding for love

1. 简介

前面介绍了观察者模式,就好比我们去点餐,通知服务员说,餐好了跟我说一下。那么服务员和顾客之间就形成了耦合,首先服务员得知道餐品好了以后通知那些顾客,其次,如果是多位服务员协作,每个服务员都需要知道这些顾客。

但事实上你发现去 kfc 点餐的时候,服务员并没有直接通知我们。而是采用叫号的方式。细想一下,你去 kfc,是不是可以在点餐系统进行排号(网上或者排队,这里抽象一下),餐品好了以后,服务员输入点餐号,点一下完成即可,点餐系统会通知对应的顾客取餐。

这里你和服务员之间的消息通过点餐系统来传递,你并不需要知道是谁点的完成,服务员也不需要知道这份餐品给谁。完美解耦了消息的发送者和接收者。更好地是,我们在点餐或者叫号的时候其实还可以指定行为,比如说 66 号产品好了以后帮我送到 A1 桌。

再比如说炒股的时候,我们可以委托挂单,就是当股票到了某一个价格就帮你买入或者卖出,等等,例子很多。

2. 用例图

3. 实现

node 中 EventEmitter 就是这样一个典型例子。我们来简单实现一个 EventEmitter。

interface HandlerInfo {
    handler: Function;
    once?: boolean;
}
class EventEmitter {
    private events: Map<string, HandlerInfo[]> = new Map();
    on(type: string, handler: Function, once?: boolean) {
        if (!this.events.has(type)) {
            this.events.set(type, []);
        }
        (this.events.get(type) || []).push({
            handler,
            once,
        });
        return () => {
            this.off(type, handler);
        };
    }
    once(type: string, handler: Function) {
        return this.on(type, handler, true);
    }
    emit(type: string, ...args) {
        let i = 0;
        while (i < (this.events.get(type) || []).length) { // 这里每次都从 this.events 去动态读取,方中途被变更
            const handlers: HandlerInfo[] = this.events.get(type) || [];
            const { handler, once } = handlers[i];
            // 如果是一次性的,应该在调用前删除,防止这里会自己触发自己,导致无限循环或者次序错乱
            if (once) {
                handlers.splice(i--, 1);
            }
            i++;
            handler(...args); // 这里 this 就交给传入的 handler 来保证了
        }
    }
    off(type?: string, handler?: Function): void {
        if (!type) return; // 最好不要默认全部清除,不安全
        if (!handler) {
            this.events.set(type, []); // 因为这里是直接赋值清空,所以在 emit 的时候,记得每次都从 events 动态获取
            return;
        }
        this.events.set(type, (this.events.get(type) || []).filter(item => item.handler !== handler));
    }
}

const eventEmitter = new EventEmitter();

class Person {
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    weatherSubscribe(once?: boolean): Function {
        return eventEmitter.on('weather', (weather) => {
            switch (weather) {
                case '雨':
                    console.log(`${this.name}在家看电影`);
                    break;
                default:
                    console.log(`${this.name}出去玩`);
            }
        }, once);
    }
    weatherNotify(weather) {
        eventEmitter.emit('weather', weather);
    }
}

const xiaoWang = new Person('小王');
const xiaoMing = new Person('小明');
const xiaoZhang = new Person('小张');

xiaoWang.weatherSubscribe(true);
const off = xiaoMing.weatherSubscribe();

xiaoZhang.weatherNotify('雨');
xiaoZhang.weatherNotify('晴');

off();
xiaoZhang.weatherNotify('晴');

4. 小结

发布订阅模式可以说是对观察者模式的进一步抽象。

我们通过消息中心对消息进行统一处理,那么这里通知者和消费者的关系其实被弱化了,它们可以是任意对象,通知者和消费者也可以是同一个对象,这种模式甚至在非对象也可以使用,即我们只关注发布和订阅行为本身,而不关心发布订阅者是谁。

参考

从发布订阅模式入手读懂Node.js的EventEmitter源码 使用typescript 写一个简单的事件监听/发布订阅模式的类 TypeScript 设计模式之发布-订阅模式 观察者模式和发布订阅模式的区别 图解23种设计模式(TypeScript版)——前端必修内功心法 观察者模式 vs 发布订阅模式 设计模式之发布订阅模式(1) 一文搞懂发布订阅模式 github - node/lib/events github - wxpage/lib/message Node中EventEmitter理解与简单实现

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 简介
  • 2. 用例图
  • 3. 实现
  • 4. 小结
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档