• 观察者模式和发布订阅模式(下)


    发布订阅模式

    前一篇对观察者模式做了介绍,重点在于观察者和被观察者的对应关系,以及将被观察者的改变及时通知到相对应的观察者。
    这样的模式基本上可以解决少量数据源的情景,在观察者和被观察者可能是多对多关系的情况下,强耦合的结构会让代码不够清晰,难以维护。
    在《JavaScript设计模式》一书中,提到了Observer和Publish/Subscribe的区别。

    Observer模式要求希望接收到主题同志的观察者(或对象)必须订阅内容改变的事件。
    Publish/Subscribe模式使用了一个主题/事件通道,这个通道介于希望接收到通知(订阅者)的对象和激活事件的对象(发布者)之间。该事件系统允许代码定义应用程序的特定事件,这些事件可以传递自定义参数,自定义参数包含订阅者所需的值。其目的是避免订阅者和发布者之间产生依赖关系。

    这里的关键点在于,通过一个事件中心,将发布者和订阅者的耦合关系解开,发布者和订阅者通过事件中心来产生联系。
    打个比方,发布者像是发布小广告的,事件中心是一个调度站,订阅者则告诉事件中心,我关注A、B类型的广告,如果有更新,请通知我。调度站记录A,B类型下的订阅者,等到A,B广告发布时,通知到订阅者。
    这个例子里,发布者不关心订阅者是谁,也不维护订阅者列表,同订阅者解耦,只将自己发布的内容提交到事件中心。而订阅者和主题的关系,交给了事件中心来维护。
    画一个类图来解释一下他们的关系。
    发布订阅模式类图
    先来定义两个虚类:Publisher 和 Subscriber。

    abstract class Publisher {
        data: string;
        id: string;
        abstract publish(any);
    }
    
    abstract class Subscriber {
        id: string;
        abstract subscribe(topicId: string);
        abstract update(topicData: string);
    }
    

    接着来继承这两个类,声明两个实体类:

    class ApplePublisher extends Publisher {
        private _data;
        private _id;
        private channel;
        constructor (defaultId, defaultData, defaultChannel: TopicChannel) {
            super();
            this._id = defaultId;
            this._data = defaultData;
            this.channel = defaultChannel;
        }
    
        get id () {
            return this._id;
        }
    
        set id (newId) {
            this._id = newId;
        }
    
        get data () {
            return this._data;
        }
    
        set data (newData) {
            this._data = newData;
        }
    
        publish () {
            this.channel.publishBaseId(this.id);
        }
    }
    
    class FruitSubscriber extends Subscriber {
    
        readonly _id;
        readonly _publishId;
        private channel;
    
        constructor (id: string, publishId: string, topicChannel: TopicChannel) {
            super();
            this._id = id;
            this._publishId = publishId;
            this.channel = topicChannel;
    
            this.subscribe(this._publishId);
        }
    
        subscribe (topicId: string) {
            this.channel.subscribe(topicId, this);
        }
    
        update (topicData: string) {
            console.log('fruit subscriber ' + this._id + ': ' + topicData);
        }
    }
    

    最后来实现TopicChannel,这个类用来处理订阅、发布通知功能。

    class TopicChannel {
        private publisherMap;
        private subscriberMap;
    
        constructor () {
            this.publisherMap = new Map<string, Publisher>();
            this.subscriberMap = new Map<string, Array<Subscriber>>();
        }
    
        addPublisher (publisher: Publisher) {
            this.publisherMap.set(publisher.id, publisher);
        }
    
        removePublisher (publisher: Publisher) {
            this.publisherMap.delete(publisher.id)
        }
    
        clearPublisher () {
            this.publisherMap.clear();
        }
    
        subscribe (publisherId: string, subscriber: Subscriber) {
            if (this.subscriberMap.has(publisherId)) {
                this.subscriberMap.get(publisherId).push(subscriber);
            } else {
                this.subscriberMap.set(publisherId, [subscriber]);
            }
        }
    
        publishBaseId (publisherId: string) {
            if (this.publisherMap.has(publisherId)) {
                this.subscriberMap.get(publisherId).forEach((item)=>{
                    item.update(this.publisherMap.get(publisherId).data);
                })
            } else {
                console.log('There is not the publisher!');
            }
        }
    
    }
    

    TopicChannel通过TopicId来维护发布者和订阅者的关系,使得发布者和订阅者充分解耦。
    使得订阅者可以订阅多个主题,在内部根据主题的不同,执行不同的逻辑。
    发布者则完全无视订阅者的逻辑,只管将自己的内容推送到TopicChannel。

    let topicChannel = new TopicChannel();
    
    let applePublisher1 = new ApplePublisher('apple publisher1', 'foo apple1', topicChannel);
    let applePublisher2 = new ApplePublisher('apple publisher2', 'foo apple2', topicChannel);
    
    topicChannel.addPublisher(applePublisher1);
    topicChannel.addPublisher(applePublisher2);
    
    let fruitSubscriber1 = new FruitSubscriber('fruit1', 'apple publisher1', topicChannel);
    let fruitSubscriber2 = new FruitSubscriber('fruit2', 'apple publisher2', topicChannel);
    let fruitSubscriber3 = new FruitSubscriber('fruit3', 'apple publisher1', topicChannel);
    
    fruitSubscriber2.subscribe('apple publisher1');
    
    applePublisher1.publish();
    applePublisher2.publish();
    

    结果为:

    fruit subscriber fruit1: foo apple1
    fruit subscriber fruit3: foo apple1
    fruit subscriber fruit2: foo apple1
    fruit subscriber fruit2: foo apple2
    

    发布订阅模式将发布者和订阅者完全解耦,由事件中心通过topicId或者是其他唯一key来维护二者的关系,使得程序可以分割成更小,内聚更高的模块。
    与此同时,由于弱化了发布者与订阅者的关系,使得发布者难以追踪到订阅者,无法获得来自订阅者反馈;并且同一主题的订阅者之间相对透明,不能产生联动。
    以上,如有错误,敬请指正,感谢阅读。

  • 相关阅读:
    偶对学习C#以及理解.Net平台的一些看法(二,Junior Bibliography)
    聊聊编程那些破事0.Prehistory
    偶对学习C#以及理解.Net平台的一些看法(一,Prerequisites)
    [转帖]c#.net常用函数列表
    一个编程小题目引发的思考(上)
    geoTools学习笔记001(简介)
    ArcGIS Server 10安装配置(JAVA)
    ARCGIS中label(标注)和Annotation(注记
    JSTL入门开发包详解
    基于C/S的网盘设计(JAVA) 网盘源码实现部分功能
  • 原文地址:https://www.cnblogs.com/liuyongjia/p/9415008.html
Copyright © 2020-2023  润新知