发布-订阅模式(观察者模式)

来源:互联网 发布:流程 数据库设计 编辑:程序博客网 时间:2024/06/05 06:24

写在前面

参考文档《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);
原创粉丝点击