Design Pattern: Observer Pattern

Brief                            

一直對Observer Pattern和Pub/Sub Pattern有所混淆,下面打算經過這兩篇Blog來梳理這兩種模式。如有紕漏請你們指正。javascript

Use Case                          

首先咱們來面對一個老到跌渣的故事,並以從未據說過Observer Pattern爲前提。
假設要設計一個新聞訂閱系統,新聞分爲商業、體育和八卦3種,而查收終端有PC、移動終端等,後續還不斷增長新聞種類和查收終端。
需求如上,下面咱們根據OOD的方式來構建概念模型。
  新聞 <- 分類新聞
  終端 <- 分類終端
而後構造實體模型html

// 新聞相關實體模型
class NewsType{
  constructor(){}
}
class BusinessNewsType extends NewsType{}
class SportNewsType extends NewsType{}
class EntertaintmentNewsType extends NewsType{}
// 終端相關實體模型
class Term{
  getNews(news){}
}
class PCTerm extend Term{}
class MobileTerm extend Term{}

接着咱們關聯已有經驗——現實生活中的送報服務,發現用戶A、用戶B均訂閱了A報的X早報,那麼天天早上報紙剛印刷出來就會立刻送到用戶A和B那了。忽然多了家訂報有米送的Y早報,用戶A和用戶C跑去訂閱,用戶B直接就退了X早報,這時X早報爲留住老用戶就推送「老客戶免費贈送半年X晚報」,因而用戶A取消了退訂的想法。
很明顯 新聞訂閱系統 就是線下業務直挪到線上的作法,經過分析線下業務流程咱們能夠找到設計方案。線下業務流程以下:
訂閱:用戶到報社訂閱
退訂:用戶到報社退訂
分發報紙:報社向全部訂閱者分發報紙
按這思路構建 新聞訂閱系統 的原型:java

class NewsType{
  constructor(){
    this.subs = []
  }
  /* 訂閱
   * @param {Term} term - 終端
   * @returns {Boolean}
   */
  sub(term){
    // 排除
    for(let _sub of this.subs) 
      if(_sub === term) return false

    return Boolean(this.subs.push(term))
  }
  /* 退訂
   * @param {Term} term - 終端
   * @return {Boolean}
   */
  unsub(term){
    for(let i = 0, sub; sub = subs[i]; ++i)
      if(sub === term) return Boolean(this.subs.splice(i, 1))
  }
  /*
   * 分發新聞
   */
  notify(news){
    for(let sub of this.subs) sub.getNews(news)
  }
}
class BusinessNewsType extends NewsType{
  pubNews(title, content){
    var news = {title: title, content: content}
    super.notify(news)
  }
}
// definition of SportNewsType.......

class PCTerm extends Term{
  getNews(news){
    alert(news.title + ';' + news.content)
  }
}
class MobileTerm extends Term{
  getNews(news){
    console.log(news.title + ';' + news.content)
  }
}

// 主程序則做爲 新聞中心 與 用戶交互的場所
var businessNewsType = new BusinessNewsType()
var sportNewsType = new SportNewsType()
var pcTerm = new PCTerm()
var mobileTerm = new MobileTerm()
businessNewsType.sub(pcTerm)
businessNewsType.sub(mobileTerm)
sportNewsType.sub(mobileTerm)

上述原型基本勾勒出新聞訂閱系統中對象及其關聯的方式,咱們就能夠在這之上再細化和優化了。而從上述是原型咱們不難發現 新聞 與 終端 都可獨立開發,而後在主程序中作關聯便可。新聞類型 和 終端類型的增刪並不會對其餘已有的新聞類型和終端類型有影響,除了在主程序中增刪關聯外。
如今咱們做個簡單的分析總結:
1. 不穩定因素(新聞類型 和 終端類型)解耦 -> 最小化不穩定因素所影響的範圍(範圍越小,後期改動越少);
2. 關聯規則接口/契約化 -> 固化關聯規則 和 關聯發生的形式 便於後期維護。
這些是我面對未知問題的分析、解構方法,但願和你們一塊兒探討更美好的方法。框架

What Is Observer Pattern?                

Observer Pattern(觀察者模式),狹義上是指Observer/Subscriber關注Observable/Subject的狀態,並根據Observable/Subject的狀態做出響應。廣義上是指Observer/Subscriber關注Observable/Subject的狀態或行爲或二者兼備,並做出響應。函數

Roles

Observable/Subject(被觀察者):定義被觀察者的公共狀態和行爲
ConcreteObservable(具體的被觀察者):定義具體的被觀察者的狀態和行爲
Observer(觀察者):定義觀察者的公共狀態和行爲
ConcreteObserver(具體的觀察者):定義具體的觀察者的狀態和行爲優化

Two Methods: Push & Pull

上面第2節中的實現是由Observable/Subject來維護Observer組,那是否是隻能這樣呢?答案是否認的。它只是Push方式的實現,咱們還能夠採用Pull方式呢!
Push Model:推方式,也就是由Observable/Subject主動發起與Observer/Subscriber通訊,並將自身的全部信息推給Observer/Subscriber,即便大部分信息最後都沒用上。
pros: 1. 觀察者實時響應被觀察者的狀態變化和行爲情況;
cons: 1. 觀察者被硬塞一些被觀察者的無效信息;2. 被觀察者狀態變化頻密,致使觀察者忙於響應,消耗資源。
Pull Model:拉方式,也就是由主動Observer/Subscriber發起與Observable/Subject通訊,並根據自身須要從Observable/Subject上拉去有效信息。通常經過定時器或特定事件觸發執行。
pros: 1. 觀察者可按需從被觀察者處提取有效信息;2. 自主控制通訊節奏,以避免被狀態頻密變化的被觀察者牽着鼻子走;
cons: 1. 獲取被觀察者狀態變化上存在滯後甚至丟失的狀況。
下面是Pull Model的實現方式this

// Pull Model implementation
class FooNewsType{
  constructor(){
    this.news = []
  }

  addNews(title, content){
    this.news.push({title: title, content: content, timestamp:(+new Date())})
  }
}
class PCTerm{
  constructor(){
    this.subjects = []
    this.newsTiles = []
    this.lastPullDate = 0
  }

  subTo(newsType){
    this.subjects.push(newType)
  }
  unsubFrom(newsType){
    for(let i = 0, n; n = this.subjects[i]; ++i)
      if(n === newsType) return this.subjects.splice(i, 1)
  }
  // 拉數據
  pull(){
    for(let sub of this.subjects)
      for(let news of sub.news)
        if(news.timestamp > this.lastPullDate)
          this.newsTiles .push(news.title)
  }
}
// 主程序
var businessNewsType = new BusinessNewsType()
var pcTerm = new PCTerm()
pcTerm.subTo(businessNewsType)
businessNewsType .addNews('Say Hi', 'Hello World!')
// 其餘代碼........
pcTerm.pull()

Improvement of Push Model

針對Push Model所帶來的問題1,咱們能夠經過加強sub函數來解決設計

// definition
sub(term, aspect){
  this.subs.push({term: term, aspect: aspect})
}
notify(news){
  for(let sub of this.subs) 
    sub.term.getNews(sub.aspect && sub.aspect(news) || news)
}

// usage
new BusinessNewsType()
  .sub(new PCTerm()
        , (news)=>{ return news.title })

針對問題2,咱們能夠經過 定時推送通知 + 溢出通知 的方式解決,不過具體仍是看業務需求咯code

constructor(interval = 100, ceiling = 5){
  this.ceiling = ceiling
  this.timer = setInterval(()=>{
    if (!this.pools.length || !this.subs.length) return

    for(let sub of this.subs)
      for(let n of news)
        sub.term.getNews(sub.aspect && sub.aspect(n) || n)
  }, interval)
}
notify(news){
  this.pools.push(news)
  if (this.pools.length < this.ceiling) return

  var news = this.pools.splice(0, this.pools.length)
  for(let sub of this.subs)
    for(let n of news)
      sub.term.getNews(sub.aspect && sub.aspect(n) || n)
}

Specific Implementation Problems —— Making sure Subject state is self-consistent before notification

就是確保Subject狀態變化完成後,再通知Subscriber。反例以下:server

notify(news){
  for(let sub of this.subs) 
    sub.term.getNews(sub.aspect && sub.aspect(news) || news)
  // 發生在通知觀察者以後
  news.title = 'changed'
}

至關於爲每次Subject狀態的總體變化打個版本號,而後將屬於該版本的Subject狀態發送給Subscriber,以後的狀態變化就屬於下一個版本了。

Diff Between Observer Pattern and Pub/Sub Pattern

二者區別主要體如今如下2點
1. 耦合度
Observer Pattern: Subscriber 和 Subject 二者感知對方的存在,但不受對方的具體實現 和 數目 所限制 => 弱依賴。關聯規則內置在Subscriber 或 Subject中。
Pub/Sub Pattern: Publisher 和 Subscriber 二者相互間毫無存在感,經過Message Broker關聯兩種角色,而且將關聯規則藏進Message Broker中。
2. 影響範圍
  Observer Pattern做爲Design Pattern存在,而Pub/Sub Pattern則做爲Architecture Pattern存在,明顯Observer Pattern的影響範圍較小。也就是說在採用Pub/Sub Pattern時,須要更謹慎。

We Used Observer Pattern Already          

其實咱們如今用到不少框架、類庫均採用了Observer Pattern,如MVC和Event Mechanism等。
  MVC中M(odel)做爲觀察者,而V(iew)做爲被觀察者;
  而Event Mechanism則是更爲典型的Observer Pattern,C#在語法層面(event關鍵字),而Java經過內置類庫對其提供支持。

Conclusion                 

洋洋灑灑寫了這麼多,如有紕漏請你們指正,謝謝!

Thanks                      

http://wiki.jikexueyuan.com/project/javascript-design-patterns/observe...
http://www.joezimjs.com/javascript/javascript-design-patterns-observer...
http://www.oodesign.com/observer-pattern.html

相關文章
相關標籤/搜索