20190411 期
设计模式 - 如何理解观察者 (发布订阅) 模式?
定义: 观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己
生活实例理解:你今天去看一个楼盘,去了之后发布楼盘还没有对外销售,你不知道楼盘时候会对外销售,于是你找了楼盘的负责人,告诉他什么时候楼盘开始销售了电话通知你,然后想要买的人不是你一个,其它人也是通过留电话的方式给销售负责人来及时获取消息
不难发现, 上面的例子正好对应上我们的观察者模式的定义, 多个想要买房的人同时订阅了一个主题 (楼盘对外销售),这个主题更新时,这些观察者(买房) 都会作出相应的动作
最熟悉的代码理解:
实际上,我们经常用到的事件绑定就是发布订阅模式
在这里我们想在用户点击的时候做出相应的处理,但是我们不知道用户在什么时候去点击,所以我们去订阅 body 上的 click 事件,在这里我们还可以去随意增加订阅者,这样并不影响我们的发布者
document.body.addEventListener(‘click’,function(){
console.log(‘JS 每日一题 ’)
},false)
document.body.addEventListener(‘click’,function(){
console.log(‘ 今天你打卡了吗?’)
},false)
实现一个简版的观察者
首先我们顺一下思路
谁是发布者
谁是订阅者
发生改变时怎么通知订阅者作出相应动作
const Boss = {} // 楼盘销售负责人
Boss.clientList = []; // 存放订阅者的回调
Boss.listen = function(fn){// 增加订阅者
this.clientList.push(fn); // 将买房人的号码缓存起来
}
Boss.trigger = function(){ // 发布消息
for(var i=0,fn; fn= this.clientList[i++];){
fn.apply(this,arguments)
}
}
Boss.listen(function(msg){
console.log(msg) // 开始销售了
})
cdBoss.trigger(‘ 开始销售了 ’) // 发布消息
我们已经实现在最简易版的发布订阅,但其实是存在问题的,每个人可能订阅户型是不同的, 上面我们实现的是,只要一开始销售就通知所有订阅的人,显然是不合理的,我们将代码再来改写一下
// 订阅时给其加一个 key 做为标识,就相当于 key 就是订阅者的身份
Boss.listen = function(key, fn){
if (!this.clientList[key]) {
// 如果没有此类订阅,就给该类订阅增加一个缓存列表
this.clientList[key] = []
}
this.clientList[key].push(fn);
}
Boss.trigger = function(){ // 发布消息
const key = Array.prototype.shift.call(arguments),
// 取出消息类型
fns = this.clientList[key]
if (!fns || !fns.length) return // 如果该类消息没有订阅直接返回
for (var i = 0, fn; fn = fns[i++]) {
fn.apply(this, arguments)
}
}
Boss.listen(‘ 老王 ’, function (msg) {
console.log(‘ 老王订阅户型 ’ + cdName)
})
Boss.listen(‘ 老李 ’, function (cdName) {
console.log(‘ 老李订阅户型 ’ + cdName)
})
Boss.trigger(‘ 老王 ’, ‘143 平米 ’);
Boss.trigger(‘ 老李 ’, ‘888 平米 ’);
好了,经过改写,消息只会推送给相关的订阅者了
总结
时间上的解藕
对象之间的解藕
总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化
关于 JS 每日一题
JS 每日一题可以看成是一个语音答题社区 每天利用碎片时间采用 60 秒内的语音形式来完成当天的考题 群主在次日 0 点推送当天的参考答案
注 绝不仅限于完成当天任务,更多是查漏补缺,学习群内其它同学优秀的答题思路
点击加入答题