写在前面
参考文档《JavaScript设计模式与实战》
发布-订阅模式
光是看名字还是很好理解的,也就是在生活中,我们常常受到关于某品牌的活动消息(虽然大多数都是默认订阅),此时收到信息的我们就是订阅者,而品牌方则是发布者。
发布-订阅的模式也有很多好处:不需要我们每次都去询问活动什么时候举办。解耦的操作,品牌方不需要关心订阅者的其他消息,只需要按照名单发送短信即可。
主要的内容
我们可以看到这个模式中有三个主要的角色和内容:1. 发布者 2. 订阅者 3. 订阅名单列表
下面是实现
let saler = {};
// 存放顾客名单
saler.list = [];
// 添加订阅消息的顾客
saler.listen = function(fn) {
this.list.push(fn);
}
// 发布消息
saler.trigger = function() {
for(let i in this.list) {
this.list[i].apply(this, arguments);
}
}
实践
saler.listen(function(price, area) {
console.log('price is '+price+' area is '+ area);
})
saler.trigger(200, 100); // price is 200 area is 100
有没有发现奇怪的一点?
就是,一旦saler发布一个消息,所有的订阅者都会收到消息,不论他们是否订阅了该消息。
我只想收到我需要的
于是乎,这就涉及到了分类问题。
因此,我们加上一个key
。这个key
是用与给订阅不同消息的用户进行分类,订阅相同消息的用户将使用同一个key进行订阅消息。
let saler = {};
// 修改客户列表为对象,key为属性值,每个属性都维护一个顾客数组
saler.list = {};
saler.listen = function(key, fn) {
if(!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
}
// 根据key来发布消息,这样只会发布到订阅相同key的顾客手机上
saler.trigger = function() {
let key = arguments[0];
if(!key || !this.list[key]) {
return false;
}
for(let i in this.list[key]) {
this.list[key][i].apply(this, arguments);
}
}
实践
saler.listen('Activity1', function(price, area) {
console.log('It\'s ' + arguments[0]);
console.log('price is '+arguments[1]+' area is '+ arguments[2]);
})
saler.listen('Activity2', function() {
console.log('It\'s ' + arguments[0]);
console.log('price is '+arguments[1]+' area is '+ arguments[2]);
})
// It's Activity1, price is ..
saler.trigger('Activity1' ,200, 100);
// It's Activity2, price is ..
saler.trigger('Activity2' ,300, 300);
// 什么都不输出
saler.trigger('Activity3', 100, 100);
这就解决了订阅不同消息时发布到不同的顾客手中。
现在又出现了一个问题,如果有50个品牌方,手中都有顾客名单,也要发布相关活动信息,这样不是得写50个上面的对象?
因此需要实现一个通用的版本,即所有品牌方都可以通过某个对象上的方法进行发布内容。
通用版本
首先实现一个类,这个类中有上面所叙述的所有属性和方法。
let Saler = function(name) {
this.name = name;
this.list = {};
this.listen = function(key, fn) {
if(!this.list[key]) {
this.list[key] = [];
}
this.list[key].push(fn);
};
this.trigger = function() {
let key = arguments[0];
if(!key || !this.list[key]) {
return false;
}
for(let i in this.list[key]) {
this.list[key][i].apply(this, arguments);
}
};
}
现在有两个品牌方,一个是Beauty
,另一个是Handsome
。他们通过new
来创建新的对象,这个对象有上述的所有方法。
let saler = new Saler('Beauty');
let saler2 = new Saler('Handsome');
顾客订阅他们的活动
saler.listen('Activity1', function(price, area) {
console.log('Name of Brand: '+ this.name);
console.log('It\'s ' + arguments[0]);
console.log('price is '+arguments[1]+' area is '+ arguments[2]);
})
saler2.listen('Activity1', function(price, area) {
console.log('Name of Brand: '+ this.name);
console.log('It\'s ' + arguments[0]);
console.log('price is '+arguments[1]+' area is '+ arguments[2]);
})
品牌方分别发布他们的活动
// "Name of Brand: Beauty"
// "It's Activity1"
// "price is 200 area is 100"
saler.trigger('Activity1' ,200, 100);
// "Name of Brand: Handsome"
// "It's Activity1"
// "price is 2000 area is 10"
saler2.trigger('Activity1', 2000, 10);
不好意思,我要取消订阅
顾客是上帝,她说订就订,她说取消咱们也麻利点取消订阅。
显然,取消订阅只需要在她订阅的活动的名单中删除掉这个顾客就好了。
添加一个remove
方法。
this.remove = function(key, fn) {
if(!key || !this.list[key]) {
return false;
}
for(let i in this.list[key]) {
if(this.list[key][i] === fn) {
this.list[key].splice(i, 1);
console.log(this.list);
break;
}
}
}
这里需要注意的是,由于取消订阅是通过判断两个函数是否相等进行的删除,因此在listen
时不能用匿名函数。
实践
saler.listen('Activity1', fn1 = function(price, area) {
console.log('Name of Brand: '+ this.name);
console.log('It\'s ' + arguments[0]);
console.log('price is '+arguments[1]+' area is '+ arguments[2]);
})
saler.remove('Activity1', fn1);