發佈-訂閱模式(觀察者模式)

寫在前面

參考文檔《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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章