js设计模式-订阅-发布模式
最近在看js设计模式有关的书籍,所以想接着记录下比较常用的一些知识,就勉强算是一遍读书笔记吧
定义
发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状 态发生改变时,所有依赖于它的对象都将得到通知。
生活中的例子
作为一个女生,最近很关心宫颈癌疫苗这个玩意,听说是一种预防宫颈癌发病的疫苗。自行出了这个疫苗,众多女性拍手一致叫好,毕竟人都怕死,万一哪天要game over了,却发现曾经有避免的机会,那必须后悔死呀-_-!!!。那么有了这个疫苗,女生势必是要关注的,但不是所有医院都能注射,即使医院能注射也不能保证有现货的了,何况疫苗还分个2,4,9价。最近小美就发现海淀医院可以注射这个疫苗,因此小美就每天都打电话过去问是否已经有了疫苗,但是除了小美还有小明,小欧等也会每天向医院询问,医院接电话的小姐姐差点决定辞职不干了,因为每天都是重复的问题。
不过都9012年了,自然是没有这么傻的处理方式,事实上是让每位有需求的客人都在医院中留下各自的信息,联系电话等,他们的信息都被医院记录在册,等医院有了疫苗就会通知各位。日常中还有卖楼及各种信息的推送都属于发布-订阅的模式
分析一下:在等待疫苗的例子中,使用发布订阅的模式有着许多显而易见的有点:
- 顾客不用再每天都打电话咨询,在合适的时候医院就会发布出对应的消息进行告知
- 医院也不需要关心顾客的其他信息,只要知道联系方式即可,不会有太多的耦合
实现这个小小的例子
- 首先定义一个发布者(医院)
- 发布者的缓存列表,存放回调函数方便通知订阅者(相当于医院的记录信息的册子)
- 发布信息的时候,发布者遍历这个缓存列表,依次出发订阅者 的回调函数(挨个发消息)
回调函数中需要加上一些参数,订阅者可以接受这些参数,这些参数代表着医院给出的关于疫苗的价格,日期等这些附加信息
var hospitalCenter = {}; // 定义一个发布者 =》 医院疫苗处
hospitalCenter.infoBrochure = []; // 定义医院疫苗处需要给发消息的名册
hospitalCenter.subscribe = function(fn) { // 增加订阅者
this.infoBrochure.push(fn) // 订阅消息添加进缓存列表
}
hospitalCenter.pubulish = function () { // 发布消息
for(var i = 0, fn; fn = this.infoBrochure[i++];) {
fn.apply(this, arguments) // arguments发布消息时带上的参数
}
}
// 小美订阅,小张都订阅了
hospitalCenter.subscribe(function(price, vaccineType) {
console.log(vaccineType + '疫苗在此医院的价格为:' + price)
})
hospitalCenter.pubulish(10000, '二价')
hospitalCenter.pubulish(20000, '四价')
// 输出结果
// 二价疫苗在此医院的价格为:10000
// 四价疫苗在此医院的价格为:20000
再次我们看到了,小张和小美都是是订阅了这个医院的疫苗信息,因此医院在发布消息的时候,小美和小张都收到了,虽然说这样是实现了发布-订阅的功能,但是,发现了吗?他们是不分条件的收到了医院发出的所有信息,即使小美只关注二价疫苗,但依然收到了四价的,这样对于小美就是垃圾信息了。
我们优化下,增加一个标示 key, 让订阅者只订阅自己感兴趣的消息
// 按照订阅的的需求给发布消息
hospitalCenter.subscribe = function(key, fn) { // key:订阅的消息类别
if(!this.infoBrochure[key]) { // 如果消息类别中无此类,就给该类消息创建一个缓存列表
this.infoBrochure[key] = []
}
this.infoBrochure[key].push(fn)
}
hospitalCenter.pubulish = function() {
var key = Array.prototype.shift.call(arguments), // 取出消息类别
fns = this.infoBrochure[key]; // 该消息类别下的回调集合
if(!fns || fns.length === 0){ // 该类下无消息或没有订购此消息
return false
}
for(var i = 0, fn; fn=fns[i++];) {
fn.apply(this, arguments)
}
}
// 小美订阅二价
hospitalCenter.subscribe('二价', function(price) {
console.log('二价疫苗在此医院的价格为' + price)
// console.log('squareMeter: ' + squareMeter)
})
// 小张订阅四价
hospitalCenter.subscribe('四价', function(price) {
console.log('四价疫苗在此医院的价格为' + price)
// console.log('squareMeter: ' + squareMeter)
})
// 小李订阅九价
hospitalCenter.subscribe('九价', function(price) {
console.log('九价疫苗在此医院的价格为' + price)
// console.log('squareMeter: ' + squareMeter)
})
// 医院发布
hospitalCenter.pubulish('二价', 3000)
hospitalCenter.pubulish('四价',4000)
hospitalCenter.pubulish('九价',6000)
// 二价疫苗在此医院的价格为3000
// 四价疫苗在此医院的价格为4000
// 九价疫苗在此医院的价格为6000
这样的情况下,每位来医院的客人都能订阅自己只关注的的信息了
现在又有一个问题,北京的医院大大小小不计其数,小美离海淀医院近,便直接去了海淀医院,但是小张距离北大一医院近,因此决定直接去北大一。那么这段代码是不是在每个医院都得写上一遍呢??这样是不是太傻了点,我能不能直接让每个对象都拥有发布-订阅的功能?答案那必须的,接下来继续看:
我们定义一个事件:
var eventObj = {
infoBrochure: [],
subscribe: function(key , fn) {
if(!this.infoBrochure[key]) {
this.infoBrochure[key] = []
}
this.infoBrochure[key].push(fn) // 订阅的消息添加进缓存列表
},
publish: function(){
var key = Array.prototype.shift.call(arguments),
fns = this.infoBrochure[key];
if(!fns || fns.length === 0){ // 该类下无消息或没有订购此消息
return false
}
for(var i = 0, fn; fn=fns[i++];) {
fn.apply(this, arguments)
}
},
remove: function(key, fn) {
var fns = this.infoBrochure[key]
if(!fns) { // 如果 key 对应的消息没有被人订阅,则直接返回
return false
}
if(!fn) {
fns && (fns.length = 0)
} else {
for(var j = fns.length; j >= 0; j--) { // 反向遍历订阅的回调函数列表
var fnsInfo = fns[j];
if(fnsInfo === fn) {
fns.splice(j,1) // 删除订阅者的回调函数
}
}
}
}
}
var installEvent = function(obj) {
for(var i in eventObj) {
obj[i] = eventObj[i]
}
}
var hospitalCenter = {}
installEvent(hospitalCenter)
hospitalCenter.subscribe('二价', fn1 = function(price){
console.log('价格=' + price)
})
hospitalCenter.subscribe('四价', fn2 = function(price){
console.log('四价价格=' + price)
})
hospitalCenter.remove('二价', fn1)
hospitalCenter.publish('四价', 5000)
// 四价价格=5000
这样的情况下,这个对象就具有了发布,订阅和删除的功能了
发布—订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦