设计模式的定义: 在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案。
设计模式并不能直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案,它不是一个死的机制,它是一种思想,一种代码的形式。
每种语言对于各种设计模式都要它们自己的实现方式,对于某些设计模式来说,可能在某些语言下并不适用,比如工厂模式就不适用于JavaSctipt
。模式应该用在正确的地方,而所谓正确的地方只有我们深刻理解模式的意图后,再结合项目的实际场景才知道。
观察者模式定义了对象间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,并自动更新。
观察者模式属于行为模式,行为模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。
观察者模式还有一个别名叫“发布-订阅模式”,又或者“订阅-发布模式”,订阅者和订阅目标是联系在一起的,当订阅目标发生改变时,逐各通知订阅者。
我们用报纸期刊的订阅来举例说明,当你订阅一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸,报社和订报纸的客户就是定义里描述的“一对多”的依赖关系。
其实24种基本设计模式中,并没有发布-订阅模式,上面也解释了,它只是观察者模式的一个别称。但经过时间的沉淀,它已经强大起来,已经独立于观察者模式,成为一种新的设计模式。
在现在的发布-订阅模式中,发布者的消息不会直接发送给订阅者,这意味着发布者和订阅者都不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息,并相应的分发给它们的订阅者。
举个例子,你在微博上关注了A,同时其他很多人也关注了A,当A发布动态时,微博就会为你们推送这条动态。A就是发布者,你就是订阅者,微博是调度中心,你和A是没有直接的消息往来的,全是通过微博来协调的。
观察者模式: 观察者(Observer)直接订阅(Subscribe)主体(Subject),而当主体被激活时,会触发(Fire Event)观察者里的事件。
发布-订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
// 有一家猎人公会,其中每个猎人都具体发布任务(publish),订阅任务(subscribe)的功能
// 它们都有一个订阅列表记录谁订阅了自己
// 定义一个猎人,包括姓名、级别、订阅列表
function Hunter(name, level) {
this,name = name
this.level = level
this.list = []
}
Hunter.prototype.publish = function (money) {
console.log(this,level + '猎人: ' + this.name + '寻求帮助')
this.list.forEach(function (callback) {
callback && callback(money)
})
}
Hunter.prototype.subscribe = function (target, callback) {
console.log(this.level + '猎人: ' + this.name + '订阅了: ' + target.name)
target.list.push(callback)
}
// 猎人公会走注册了几个猎人
var hunterZhang = new Hunter('张三', '钻石')
var hunterLi = new Hunter('李四', '黄金')
var hunterWang = new Hunter('王五', '白银')
var hunterZhao = new Hunter('赵六', '青铜')
// 赵六等级较低,可能需要帮助,所以张三、李四、王五都订阅了赵六
hunterZhang.subscribe(hunterZhao, function (money) {
console.log('小明表示: ' + (money > 200 ? '' : '暂时很忙,不能') + '给予帮助')
})
hunterLi.subscribe(hunterZhao, function () {
console.log('李四表示: 给予帮助')
})
hunterWang.subscribe(hunterZhao, function () {
console.log('王五表示: 给予帮助')
})
// 赵六遇到困难,悬赏198寻求帮助
hunterZhao.publish(198)
// 猎人们(观察者)关联他们感兴趣的猎人(目标对象),如赵六,当赵六有困难时,会自动通知给他们(观察者)
// 定义了一家猎人公会
// 主要功能包含任务发布大厅(topics)、订阅任务(subscribe)、发布任务(publish)
var HunterUnion = {
type: 'hunt',
topics: Object.create(null),
subscribe: function (topic, callback) {
if (!this.topics[topic]) {
this.topics[topic] = []
}
this.topics[topic].push(callback)
},
publish: function (topic, money) {
if (!this.topics[topic]) {
return
}
for(var cb of this.topics[topic]) {
cb(money)
}
}
}
// 定义一个猎人类,包括姓名和级别
function Hunter(name, level) {
this.name = name
this.level = level
}
// 猎人可以在猎人公会发布、订阅任务
Hunter.prototype.subscribe = function (task, fn) {
console.log(this.level + '猎人: ' + this.name + '订阅了狩猎: ' + task + '的任务')
HunterUnion.subscribe(task, fn)
}
Hunter.prototype.publish = function (task, money) {
console.log(this.level + '猎人: ' + this.name + '发布了狩猎: ' + task + '的任务')
HunterUnion.publish(task, money)
}
//猎人工会注册了几个猎人
let hunterZhang = new Hunter('张三', '钻石')
let hunterLi = new Hunter('李四', '黄金')
let hunterWang = new Hunter('王五', '白银')
let hunterZhao = new Hunter('赵六', '青铜')
//张三,李四,王五分别订阅了狩猎tiger的任务
hunterZhang.subscribe('tiger', function(money){
console.log('张三表示:' + (money > 200 ? '' : '不') + '接取任务')
})
hunterLi.subscribe('tiger', function(money){
console.log('李四表示:接取任务')
})
hunterWang.subscribe('tiger', function(money){
console.log('王五表示:接取任务')
})
//赵六订阅了狩猎sheep的任务
hunterZhao.subscribe('sheep', function(money){
console.log('赵六表示:接取任务')
})
//赵六发布了狩猎tiger的任务
hunterZhao.publish('tiger', 198)
//猎人们发布(发布者)或订阅(观察者/订阅者)任务都是通过猎人工会(调度中心)关联起来的,他们没有直接的交流。
观察者模式和发布-订阅模式最大的区别: 发布-订阅模式有事件调度中心。
观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,这种处理方式可能会造成代码的冗余。
发布-订阅模式中,统一由调度中进行处理,订阅者和发布者互不干扰,消除了发布者和订阅者之间的依赖。这样一方面实现了解耦,另一方面可以实现更加细粒度的控制。比如发布者发布了很多消息,但不是所有的订阅者都希望接收到,就可以在调度中心做一些处理,类似权限控制之类的。还可以做一些节流操作。
《JavaScript设计模式与开发实践》一书中说到分辨模式的关键是意图而不是结构。
如果以结构来分辨模式,发布-订阅模式比观察者模式多了一个调度中心,所以发布-订阅模式不同于观察者模式。
如果以意图来分辨模式,它们都实现了对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知,并自动更新,那么它们就是同一种模式,发布-订阅模式是在观察者模式的基础上做了优化升级。
不过不管它们是不是同一个设计模式,它们的实现方式的确是有区别,我们在使用时应该根据应用场景来判断选择哪个。