美文网首页
TS 设计模式08 - 发布订阅模式

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

作者: love丁酥酥 | 来源:发表于2020-09-18 03:15 被阅读0次

    1. 简介

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

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

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

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

    2. 用例图

    image.png

    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('晴');
    
    image.png

    4. 小结

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

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

    参考

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

    相关文章

      网友评论

          本文标题:TS 设计模式08 - 发布订阅模式

          本文链接:https://www.haomeiwen.com/subject/lirdyktx.html