Javascript 设计模式 - 发布订阅模式

1,263 阅读5分钟

实现发布订阅模式

介绍

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript开发中,一般用事件模型来替代传统的发布—订阅模式。

应用

  • dom的clickfocus等事件
  • 在Vue中的任意组件件通信事件总线EventBus
  • 含有订阅功能的系统中,如新闻app中订阅了报纸新闻;

源码实现

使用javascript实现发布订阅模式很简单,只需要将订阅者的回调函数存储起来即可。

简单版

/**
 * 发布订阅模式
 */
class PublishSubscribePattern {
    constructor() {
        // 消息映射
        this.msgMap = {};
    }
    // 发布
    publish(name, param) {
        const msg = this.msgMap[name];
        if (msg) {
            msg.subscribes.forEach(subscribe => {
                subscribe.callback(param);
            });
        } else {
            console.log('无人订阅此消息:', name, param);
        }
    }
    // 订阅
    subscribe(name, callback) {
        const msg = this.msgMap[name];
        if (msg) {
            msg.subscribes.push({callback});
        } else {
            this.msgMap[name] = {
                name,
                subscribes: [{callback}]
            }
        }
    }
}
  • 使用
const event = new PublishSubscribePattern();
event.publish('news', 'this is news 1');
event.subscribe('news', (param) => {
    console.log('get news:', param);
});
event.publish('news', 'this is news 2');

带发布者版本

订阅者希望只订阅某一个发布者发布的消息

  • 源码
/**
 * 发布订阅模式
 */
class PublishSubscribePattern {
    constructor() {
        // 消息映射
        this.msgMap = {};
    }
    // 发布
    publish({name, param, publisher}) {
        const msg = this.msgMap[name];
        if (msg) {
            if (!publisher)  {
                throw new Error('未注册发布人:' + name);
            } else if (publisher === 'all') {
                msg.subscribes.forEach(e => e.callback(param));
            } else {
                let beAccept = false;
                msg.subscribes.forEach(e => {
                    if (e.publisher === publisher) {
                        beAccept = true;
                        e.callback(param);
                    }
                });
                if (!beAccept) {
                    console.log('无人订阅你的消息:', name, param);
                }
            }
        } else {
            console.log('无人订阅此消息:', name, param);
        }
    }
    // 订阅
    subscribe({name, publisher}, callback) {
        const msg = this.msgMap[name];
        if (msg) {
            msg.subscribes.push({
                publisher,
                callback
            });
        } else {
            this.msgMap[name] = {
                name,
                subscribes: [{
                    publisher,
                    callback
                }]
            }
        }
    }
}
  • 使用
const event = new PublishSubscribePattern();
event.publish({name: 'news', param: 'this is news 1', publisher: 'weather'});

event.subscribe({name: 'news', publisher: 'weather'}, (param) => {
    console.log(`get news from weather:`, param);
});

event.publish({name: 'news', param: 'this is news 2', publisher: 'weather'});
event.publish({name: 'news', param: 'this is news of newspaper', publisher: 'newspaper'});

/*
无人订阅此消息: news this is news 1
get news from weather: this is news 2
无人订阅你的消息: news this is news of newspaper
*/

复杂版

复杂版主要实现以下功能:

  • 取消订阅功能

  • 订阅者希望只订阅某一个发布者发布的消息

  • 订阅者希望获取到在订阅之前发布的历史消息

  • 订阅者希望查看自己订阅了哪些消息

  • 发布者希望查看自己发布过哪些消息

  • 代码

/**
 * 发布订阅模式
 */
class Event {
    constructor() {
        // 发布者映射
        this.publisherMap = {};
        // 订阅者映射
        this.subscriberMap = {};
    }
    // 注册
    registration(username, type = 'all') {
        if (type === 'publisher') {
            this.publisherMap[username] = {
                publisher: username,
                msgMap: {}
            };
            return this.publisherMap[username];
        } else if (type === 'subscriber') {
            this.subscriberMap[username] = {
                subscriber: username,
                msgMap: {}
            };
            return this.subscriberMap[username];
        } else if (type === 'all') {
            this.publisherMap[username] = {
                publisher: username,
                msgMap: {}
            };
            this.subscriberMap[username] = {
                subscriber: username,
                msgMap: {}
            };
        }
    }
    // 发布
    publish({name, param, publisher}) {
        const publisherObj = this.publisherMap[publisher];
        if (!publisherObj)  {
            throw new Error('未注册发布人:' + name);
        } else {
            const historyRecord = {
                name,
                param,
                publisher,
                time: Date.now()
            };
            const msg = publisherObj.msgMap[name];
            if (msg) {
                let beAccept = false;
                msg.subscribes.forEach(e => {
                    if (e.publisher === publisher) {
                        beAccept = true;
                        e.callback(param, {name, param, publisher});
                        console.log(e.subscriber, '收到了', e.publisher, '发布的消息', name, param);
                    }
                });
                if (!beAccept) {
                    console.log('无人订阅你的消息:', name, param);
                }
                msg.history.push(historyRecord);
            } else {
                publisherObj.msgMap[name] = {
                    name,
                    publisher: publisher,
                    subscribes: [],
                    history: [historyRecord]
                };
                console.log('发布者', publisher, '注册消息:', name, param);
            }
        }
    }
    // 订阅
    subscribe({name, publisher, subscriber, receiveHistoryMsg}, callback) {
        const publisherObj = this.publisherMap[publisher];
        if (subscriber) {
            const subscriberObj = this.subscriberMap[subscriber];
            if (subscriberObj) {
                subscriberObj.msgMap[name] = {
                    name,
                    publisher,
                    subscriber: subscriber,
                    callback,
                    time: Date.now()
                };
            }
        }
        if (publisherObj) {
            const msg = publisherObj.msgMap[name];
            if (msg) {
                msg.subscribes.push({
                    publisher,
                    subscriber,
                    callback
                });
                console.log(subscriber || '游客', '订阅了', publisher, '的消息:', name);
                if (receiveHistoryMsg === true) {
                    msg.history.forEach(e => callback(e.param, e)); 
                }
            } else {
                console.log('发布者', publisher, '未注册过此消息:', name);
            }
        } else {
            console.log('发布者未注册:', publisher);
        }
    }
    // 取消订阅
    unsubscribe({name, publisher, subscriber}) {
        const publisherObj = this.publisherMap[publisher];
        if (subscriber) {
            const subscriberObj = this.subscriberMap[subscriber];
            if (subscriberObj) {
                delete subscriberObj.msgMap[name];
            }
        }
        if (publisherObj) {
            const msg = publisherObj.msgMap[name];
            if (msg) {
                msg.subscribes = msg.subscribes.filter(e => !(e.publisher === publisher && msg.name === name));
            } else {
                console.log('发布者', publisher, '未注册过此消息:', name);
            }
        } else {
            console.log('发布者未注册:', publisher);
        }
    }
    // 获取发布历史消息
    getPublishHistory(publisher, name) {
        return this.publisherMap[publisher].msgMap[name].history;
    }
    getSubscribeMsg(subscriber) {
        return this.subscriberMap[subscriber].msgMap;
    }
}

  • 使用
// 直接使用Event实现发布订阅功能
const event = new Event();

const publisher = 'A';

const subscriber = 'B';

event.registration(publisher);
event.registration(subscriber);

const name = 'news';

const param = '一条消息a';

event.publish({name, publisher, param});

event.subscribe({name, publisher, subscriber, receiveHistoryMsg: true}, (param, e) => {
    console.log(`---- 接收消息from:`, param, e);
});

event.publish({name, publisher, param: '一条消息b'});

console.log('订阅的消息', event.getSubscribeMsg(subscriber));

event.unsubscribe({name, publisher, subscriber});

event.publish({name, publisher, param: '一条消息c'});

console.log('发布历史', event.getPublishHistory(publisher, name));

/*
发布者 A 注册消息: news 一条消息a
B 订阅了 A 的消息: news
---- 接收消息from: 一条消息a { name: 'news',
  param: '一条消息a',
  publisher: 'A',
  time: 1603011782573 }
---- 接收消息from: 一条消息b { name: 'news', param: '一条消息b', publisher: 'A' }
B 收到了 A 发布的消息 news 一条消息b
订阅的消息 { news:
   { name: 'news',
     publisher: 'A',
     subscriber: 'B',
     callback: [Function],
     time: 1603011782575 } }
无人订阅你的消息: news 一条消息c
发布历史 [ { name: 'news',
    param: '一条消息a',
    publisher: 'A',
    time: 1603011782573 },
  { name: 'news',
    param: '一条消息b',
    publisher: 'A',
    time: 1603011782577 },
  { name: 'news',
    param: '一条消息c',
    publisher: 'A',
    time: 1603011782578 } ]
*/

使用适配器类

  • 代码
/**
 * 代理类,屏蔽重复设置发布者、订阅者
 */
class Factory {
    constructor(username, type) {
        this.username = username;
        this.type = type;
        this._event = new Event();
        this._event.registration(username, type || 'all');
    }
    // 发布
    publish(param) {
        return this._event.publish(Object.assign({}, param, {publisher: this.username}))
    }
    // 订阅
    subscribe(param, callback) {
        return this._event.subscribe(Object.assign({}, param, {subscriber: this.username}), callback);
    }
    // 取消订阅
    unsubscribe(param) {
        return this._event.unsubscribe(Object.assign({}, param, {subscriber: this.username}));
    }
    // 获取历史发布消息
    getPublishHistory(name) {
        return this._event.getPublishHistory(this.username, name);
    }
    // 获取订阅的消息列表
    getSubscribeMsg() {
        return this._event.getSubscribeMsg(this.username);
    }

}

  • 使用
// 使用适配器封装
const publisherA = 'A';
const subscriberB = 'B';
const publisher = new Factory(publisherA, 'publisher');
const subscriber = new Factory(subscriberB, 'subscriber');

const name = '新闻';

publisher.publish({name, param: 'this is news 1'});

subscriber.subscribe({name, publisher: publisherA, receiveHistoryMsg: true}, (param) => {
    console.log(`---- get news from ${publisherA}:`, param);
});
console.log('订阅的消息', subscriber.getSubscribeMsg());

publisher.publish({name, param: 'this is news 2'});

publisher.publish({name, param: 'this is news of newspaper'});

console.log('发布历史', publisher.getPublishHistory(name));

/*
发布者 A 注册消息: 新闻 this is news 1
发布者未注册: A
订阅的消息 { '新闻':
   { name: '新闻',
     publisher: 'A',
     subscriber: 'B',
     callback: [Function],
     time: 1603012329816 } }
无人订阅你的消息: 新闻 this is news 2
无人订阅你的消息: 新闻 this is news of newspaper
发布历史 [ { name: '新闻',
    param: 'this is news 1',
    publisher: 'A',
    time: 1603012329813 },
  { name: '新闻',
    param: 'this is news 2',
    publisher: 'A',
    time: 1603012329819 },
  { name: '新闻',
    param: 'this is news of newspaper',
    publisher: 'A',
    time: 1603012329819 } ]
*/