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

两年前写过一篇关于消息发布订阅的文章,当时是结合了实际的应用场景。看起来不够抽象,概括。今天试图抽象话的再去写一下自己的理解。

此文续上篇发布 订阅 消息系统(一),可点击查看,取名改为观察者和发布订阅模式。

什么是观察者模式?

观察者模式(Observer Pattern)定义了一种当对象之间存在一对多的关系时的一种行为模式。比如当一个对象被修改时,则会通知它的依赖对象并自动更新状态。它主要解决了一个对象状态改变给其他对象通知的问题,并且考虑到易用和低耦合度,保证高度的协作性。

比如一个应用的实例:军训时,教官(目标对象)的状态发生改变,发出“立正”的口令,则所有的学生(观察者对象)此时得到通知,状态随之更新,做出"立正“的动作响应。

一个极简的观察者模式代码案例

该案例的特征:

  • 一个目标者对象 Subject,拥有方法:添加 / 删除 / 通知 Observer;
  • 多个观察者对象 Observer,拥有方法:接收 Subject 状态变更通知并处理;
  • 目标对象 Subject 状态变更时,通知所有 Observer。
//发布者
class Subject {
	constructor() {
		this.observers = []
	}
	add(observer) {
		this.observers.push(observer)
	}
	delete(observer) {
		let idx = this.observers.findIndex( item => item === observer);
		idx > -1 && this.observers.splice(idx, 1)
	}
	notify() {
		for(let observer of this.observers) {
			observer.update()
		}
	}		
}

//观察者
class Observer {
	constructor(name) {
		this.name= name
	}
	update() {
		console.log(`我是${this.name},我立正了`)
	}
}
// 实例化目标者
let subject = new Subject();

// 实例化两个观察者
let obs1 = new Observer('刘德华');
let obs2 = new Observer('吴彦祖');

// 向目标者添加观察者
subject.add(obs1);
subject.add(obs2);

// 目标者通知更新
subject.notify();
//打印结果
//我是刘德华,我立正了
//我是吴彦祖,我立正了

上面代码可以添加、删除观察者,当然你可以定制自己需求的功能函数。

当然这种模式是存在一些不足的,比如:

  • 一个目标者有很多观察者的话,将所有通知者都通知到会花费不少时间
  • 若目标者和观察者之间存在循环依赖的话,目标发生变化,观察者变化,进而又影响目标者,处理不好,很容易导致系统崩溃。
  • 目标者通知更新的话,所有观察者都接收到了通知。比如上面教官说立正,刘同学和吴同学都是进行相应,做出立正的动作。但实际情况是,教官可能只是指定刘同学 立正,只需刘同学做出响应更新而已。

什么是发布订阅模式?

发布订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

一个极简的发布订阅模式案例

var pubsub = {
	//事件调度中心
    topics: {},
    //注册事件到调度中心
    subscribe(topic, fn) {
        this.topics[topic] = this.topics[topic] || []
        this.topics[topic].push(fn)
    },
    //将事件发布到调度中心
    publish(topic, ...args) {
        if (!this.topics[topic]) return
        for (let fn of this.topics[topic]) {
            fn(...args)
        }
    },
    //解绑到调度中心的事件
    unsubScribe(topic, fn) {
        let fnList = this.topics[topic]
        if (!fnList) return
        //若不传入指定的要取消的订阅的方法(fn),则清除所有topic下的订阅
        if (!fn) {
            fnList && (fnList.length == 0)
        } else {
            fnList.forEach((item, index) => {
                if (item == fn) {
                    fnList.splice(index, 1)
                }
            })
        }
    }
}

//注册eat事件
pubsub.subscribe('eat', time => {
    console.log(`now is ${time},time to eat lunch`);
})

//注册work事件
pubsub.subscribe('work', time => {
    console.log(`now is ${time},time to work`);
})

//发布work,eat事件
pubsub.publish('work', '8:30 AM') //now is 8:30 AM,time to work
pubsub.publish('eat', '12:30 AM') //now is 12:30 AM,time to eat lunch

//取消eat事件的订阅
pubsub.unsubScribe('eat')

发布订阅模式与观察者模式的不同,在于第三者的出现:事件中心(topic)的出现。目标对象并不直接通知观察者,而是通过事件中心来派发事件。订阅者只关心自己订阅事件以此来响应变化。

就像上文说的那样,DOM的监听事件其实也是该模式的应用。比如jQuery的onclick事件,one()只触发一次响应事件,off()解除绑定事件(类比上面代码的unsubScribe事件)

借用一张图表示如下:

观察者模式和发布订阅模式(续)_第1张图片

观察者模式是不是发布订阅模式

借用一个同学的总结

网上关于这个问题的回答,出现了两极分化,有认为发布订阅模式就是观察者模式的,也有认为观察者模式和发布订阅模式是真不一样的。

其实我不知道发布订阅模式是不是观察者模式,就像我不知道辨别模式的关键是设计意图还是设计结构(理念),虽然《JavaScript设计模式与开发实践》一书中说了分辨模式的关键是意图而不是结构

如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的;如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。

不过,不管他们是不是同一个设计模式,他们的实现方式确实有差别,我们在使用的时候应该根据场景来判断选择哪个。


此文没有长篇大论,力争以最简的话语表述我所理解的内容。其中借鉴了以下三位的同学的一些理解。如果看完还是云里雾里,可以去看下他们的文章,写的很不错。

发布订阅模式与观察者模式 https://segmentfault.com/a/1190000018706349 作者:hfhan

重学JS(九)—— 观察者模式和发布/订阅模式真不一样:https://www.jianshu.com/p/f0f22398d25d 作者:闪闪发光的狼

JavaScript 设计模式(六):观察者模式与发布订阅模式 https://segmentfault.com/a/1190000019722065 作者: 以乐之名

你可能感兴趣的:(Javascript,前端)