4

做为非科班出身的前端er,每次听到设计模式都感觉很高大上,总感觉这些东西是造火箭原子弹用的,距离我们这些造螺丝钉很遥远。但是最近在做一个聊天消息的业务时,发现貌似用上发布订阅模式业务就很清晰了。创建一个消息类当作发布者,展示消息的函数是订阅者,发布者提供了注册、发布方法,订阅者注册后,每次调用发布方法修改数据时,订阅者函数自动更新数据。

class MsgList{//发布者
    constructor (){
        this.list = [];
        this.fn = []
    }
    listen(fn){//注册
      this.fn.push(fn)
    }
    add(text){//发布
        this.list.push(text)
        this.fn.map((item)=>{
            item(this.list)
        })
    }
}
function show(msg){//订阅者
    console.log(msg) //自动打印
}
var msg1 = new MsgList();
msg1.listen(show)
msg1.add('消息1')
msg1.add('消息2')

观察者模式与发布订阅模式类似。在此种模式中,一个目标物件在它本身的状态改变时主动发出通知,观察者收到通知从而使他们的状态自动发生变化。核心就是我(观察者)正在看着你(被观察者),看着你目不转睛...你只要改变我就自动改变。概念是不是很清晰了。但是观察者模式又跟发布订阅有些许不太一样的地方。

一、观察者模式

目标观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

clipboard.png

下面通过js来实现下观察者模式。首先是目标的构造函数,他有个数组,用于添加观察者。还有个广播方法,遍历观察者数组后调用他们的update方法:

class Subject{//目标类===被观察者
    constructor(){
        this.subjectList = [];//目标列表
    }
    add(fn){//注册
        this.subjectList.push(fn)
    }
    notify(context){//发通知    
        var subjectCount = this.subjectList.length
        for(var i=0; i < subjectCount; i++){
            this.subjectList[i].update(context)
        }
    }
    //取消注册
    remove(fn){
        this.subjectList.splice(this.subjectList.indexOf(fn),1)
    }
}

class Observer{//观察者类==观察者
    update(data){
        console.log('updata +' + data)
    }
}

var Subject1 = new Subject()//具体目标1
var Subject2 = new Subject()//具体目标2

var Observer1 = new Observer()//具体观察者1
var Observer2 = new Observer()//具体观察者2

Subject1.add(Observer1);//注册 //updata +test1
Subject1.add(Observer2);//注册 //updata +test1
Subject2.add(Observer1);//注册 //updata +test2

Subject1.notify('test1')//发布事件
Subject2.notify('test2')//发布事件

从上面代码可以看出来,先创建具体目标具体观察者,然后通过add方法把具体观察者 Observer1、Observer2注册到具体目标中,目标和观察者是直接联系起来的,所以具体观察者需要提供update方法。在Subject1中发通知时,Observer1、Observer2都会接收通知从而更改状态。

二、 发布/订阅模式

观察者模式存在一个问题,目标无法选择自己想要的消息发布,观察者会接收所有消息。在此基础上,出现了
发布/订阅模式,在目标和观察者之间增加一个调度中心。订阅者(观察者)把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者(目标)发布该事件到调度中心,由调度中心统一调度订阅者注册到调度中心的处理代码。

clipboard.png

class Public{//事件通道
    constructor(){
        this.handlers = {};
    }
    on(eventType, handler) { // 订阅事件
        var self = this;
        if (!(eventType in self.handlers)) {
            self.handlers[eventType] = [];
        }
        self.handlers[eventType].push(handler);
        return self ;
    }
    emit(eventType) {    // 发布事件
        var self = this;
        var handlerArgs = Array.prototype.slice.call(arguments, 1);
        var length = self.handlers[eventType].length
        for (var i = 0; i < length; i++) {
            self.handlers[eventType][i].apply(self, handlerArgs);
        }
        return self;
    }
    off(eventType, handler) {    // 删除订阅事件
        var currentEvent = this.handlers[eventType];
        var len = 0;
        if (currentEvent) {
            len = currentEvent.length;
            for (var i = len - 1; i >= 0; i--) {
                if (currentEvent[i] === handler) {
                    currentEvent.splice(i, 1);
                }
            }
        }
        return self ;
    }
}

//订阅者
function Observer1(data) {
    console.log('订阅者1订阅了:' + data)
}
function Observer2(data) {
    console.log('订阅者2订阅了:' + data)
}

var publisher = new Public();

//订阅事件
publisher.on('a', Observer1);
publisher.on('b', Observer1);
publisher.on('a', Observer2);

//发布事件
publisher.emit('a', '第一次发布的a事件');
publisher.emit('b', '第一次发布的b事件');
publisher.emit('a', '第二次发布的a事件'); 
//订阅者1订阅了:第一次发布a事件
//订阅者2订阅了:第一次发布a事件
//订阅者1订阅了:第一次发布b事件
//订阅者1订阅了:第二次发布a事件
//订阅者2订阅了:第二次发布a事件

可以看出来,订阅/发布模式下:订阅和发布是不直接调度的,而是通过调度中心来完成的,订阅者和发布者是互相不知道对方的,完全不存在耦合。


搁浅
693 声望323 粉丝

想进BAT的前端er