設計模式之觀察者模式與發佈訂閱模式

學習了一段時間設計模式,當學到觀察者模式和發佈訂閱模式的時候遇到了很大的問題,這兩個模式有點相似,有點傻傻分不清楚,博客原由如此,開始對觀察者和發佈訂閱開始了Google之旅。對整個學習過程作一個簡單的記錄。前端

觀察者模式

當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern)。好比,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行爲型模式。在觀察模式中共存在兩個角色觀察者(Observer)被觀察者(Subject),然而觀察者模式在軟件設計中是一個對象,維護一個依賴列表,當任何狀態發生改變自動通知它們。設計模式

其實觀察者模式是一個或多個觀察者對目標的狀態感興趣,它們經過將本身依附在目標對象之上以便註冊所感興趣的內容。目標狀態發生改變而且觀察者可能對這些改變感興趣,就會發送一個通知消息,調用每一個觀察者的更新方法。當觀察者再也不對目標狀態感興趣時,它們能夠簡單的將本身從中分離。數組

在觀察者模式中一共分爲這麼幾個角色:微信

  1. Subject:維護一系列觀察者,方便添加或刪除觀察者
  2. Observer:爲那些在目標狀態發生改變時須要得到通知的對象提供一個更新接口
  3. ConcreteSuject:狀態發生改變時,想Observer發送通知,存儲ConcreteObserver的狀態
  4. ConcreteObserver:具體的觀察者
舉例

舉一個生活中的例子,公司老闆能夠爲下面的工做人員分配認爲,若是老闆做爲被觀察者而存在,那麼下面所屬的那些員工則就做爲觀察者而存在,爲工做人員分配的任務來通知下面的工做人員應該去作哪些工做。網絡

經過上面的例子能夠對觀察者模式有一個簡單的認知,接下來結合下面的這張圖來再次分析一下上面的例子。架構

若是Subject = 老闆的話,那麼Observer N = 工做人員,若是細心觀察的話會發現下圖中莫名到的多了一個notify(),那麼上述例子中的工做就是notify()異步

15fe1b1f1797e09a

圖片源於網絡,侵權必刪函數

既然各個關係已經屢清楚了,下面經過代碼來實現一下上述的例子:學習

//  觀察者隊列
class ObserverList{
    constructor(){
        this.observerList = {};
    }
    Add(obj,type = "any"){
        if(!this.observerList[type]){
            this.observerList[type] = [];
        }
        this.observerList[type].push(obj);
    }
    Count(type = "any"){
        return this.observerList[type].length;
    }
    Get(index,type = "any"){
        let len = this.observerList[type].length;
        if(index > -1 && index < len){
            return this.observerList[type][index]
        }
    }
    IndexOf(obj,startIndex,type = "any"){
        let i = startIndex,
            pointer = -1;
        let len = this.observerList[type].length;
        while(i < len){
            if(this.observerList[type][i] === obj){
                pointer = i;
            }
            i++;
        }
        return pointer;
    }
    RemoveIndexAt(index,type = "any"){
        let len = this.observerList[type].length;
        if(index === 0){
            this.observerList[type].shift();
        }
        else if(index === len-1){
            this.observerList[type].pop();
        }
        else{
            this.observerList[type].splice(index,1);
        }
    }
}
//  老闆
class Boos {
    constructor(){
        this.observers = new ObserverList();
    }
    AddObserverList(observer,type){
        this.observers.Add(observer,type);
    }
    RemoveObserver(oberver,type){
        let i = this.observers.IndexOf(oberver,0,type);
        (i != -1) && this.observers.RemoveIndexAt(i,type);
    }
    Notify(type){
        let oberverCont = this.observers.Count(type);
        for(let i=0;i<oberverCont;i++){
            let emp = this.observers.Get(i,type);
            emp && emp(type);
        }
    }
}
class Employees {
  constructor(name){
    this.name = name;
  }
  getName(){
    return this.name;
  }
}
class Work {
  married(name){
    console.log(`${name}上班`);
  }
  unemployment(name){
    console.log(`${name}出差`);
  }
  writing(name){
    console.log(`${name}寫做`);
  }
  writeCode(name){
    console.log(`${name}打代碼`);
  }
}
let MyBoos = new Boos();
let work = new Work();
let aaron = new Employees("Aaron");
let angie = new Employees("Angie");
let aaronName = aaron.getName();
let angieName = angie.getName();
MyBoos.AddObserverList(work.married,aaronName);
MyBoos.AddObserverList(work.writeCode,aaronName);
MyBoos.AddObserverList(work.writing,aaronName);
MyBoos.RemoveObserver(work.writing,aaronName);
MyBoos.Notify(aaronName);

MyBoos.AddObserverList(work.married,angieName);
MyBoos.AddObserverList(work.unemployment,angieName);
MyBoos.Notify(angieName);
//  Aaron上班
//  Aaron打代碼
//  Angie上班
//  Angie出差

代碼裏面徹底遵循了流程圖,Boos類做爲被觀察者而存在,Staff做爲觀察者,經過Work二者作關聯。優化

若是相信的閱讀上述代碼的話能夠出,其實觀察者的核心代碼就是peopleList這個對象,這個對象裏面存放了N多個Array數組,經過run方法觸發觀察者的notify隊列。觀察者模式主要解決的問題就是,一個對象狀態改變給其餘對象通知的問題,並且要考慮到易用和低耦合,保證高度的協做。當咱們在作程序設計的時候,當一個目標對象的狀態發生改變,全部的觀察者對象都將獲得通知,進行廣播通知的時候,就可使用觀察者模式啦。

優勢
  1. 觀察者和被觀察者是抽象耦合的。
  2. 創建一套觸發機制。
缺點
  1. 若是一個被觀察者對象有不少的直接和間接的觀察者的話,將全部的觀察者都通知到會花費不少時間。
  2. 若是在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。
  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
小結

對於觀察者模式在被觀察者中有一個用於存儲觀察者對象的list隊列,經過統一的方法觸發,目標和觀察者是基類,目標提供維護觀察者的一系列方法,觀察者提供更新接口。具體觀察者和具體目標繼承各自的基類,而後具體觀察者把本身註冊到具體目標裏,在具體目標發生變化時候,調度觀察者的更新方法。

發佈/訂閱模式

在發佈訂閱模式上卡了好久,可是廢了好長時間沒有搞明白,也不知道本身的疑問在哪,因而就瘋狂Google不斷地翻閱找到本身的疑問,我的以爲若是想要搞明白髮布訂閱模式首先要搞明白誰是發佈者,誰是訂閱者。

發佈訂閱:在軟件架構中,發佈-訂閱是一種消息範式,消息的發送者(稱爲發佈者)不會將消息直接發送給特定的接收者(稱爲訂閱者)。而是將發佈的消息分爲不一樣的類別,無需瞭解哪些訂閱者(若是有的話)可能存在。一樣的,訂閱者能夠表達對一個或多個類別的興趣,只接收感興趣的消息,無需瞭解哪些發佈者(若是有的話)存在。-- 維基百科

看了半天沒整明白(✿◡‿◡),慚愧...因而,學習的路途不能止步,繼續...

大概不少人都和我同樣,以爲發佈訂閱模式裏的Publisher,就是觀察者模式裏的Subject,而Subscriber,就是ObserverPublisher變化時,就主動去通知Subscriber。其實並非。在發佈訂閱模式裏,發佈者,並不會直接通知訂閱者,換句話說,發佈者和訂閱者,彼此互不相識。互不相識?那他們之間如何交流?

答案是,經過第三者,也就是在消息隊列裏面,咱們常說的經紀人Broker

發佈者只需告訴Broker,我要發的消息,topicAAA,訂閱者只需告訴Broker,我要訂閱topicAAA的消息,因而,當Broker收到發佈者發過來消息,而且topicAAA時,就會把消息推送給訂閱了topicAAA的訂閱者。固然也有多是訂閱者本身過來拉取,看具體實現。

也就是說,發佈訂閱模式裏,發佈者和訂閱者,不是鬆耦合,而是徹底解耦的。

15fe1b1f07c13719

圖片源於網絡,侵權必刪

經過上面的描述終於有了一些眉目,再舉一個生活中的例子,就拿微信公衆號來講,每次微信公衆號推送消息並非一會兒推送給微信的全部用戶,而是選擇性的推送給那些已經訂閱了該公衆號的人。

老規矩吧,用代碼實現一下:

class Utils {
  constructor(){
    this.observerList = {};
  }
  Add(obj,type = "any"){
    if(!this.observerList[type]){
      this.observerList[type] = [];
    }
    this.observerList[type].push(obj);
  }
  Count(type = "any"){
    return this.observerList[type].length;
  }
  Get(index,type = "any"){
    let len = this.observerList[type].length;
    if(index > -1 && index < len){
      return this.observerList[type][index]
    }
  }
  IndexOf(obj,startIndex,type = "any"){
    let i = startIndex,
        pointer = -1;
    let len = this.observerList[type].length;
    while(i < len){
      if(this.observerList[type][i] === obj){
        pointer = i;
      }
      i++;
    }
    return pointer;
  }
}
//  訂閱者
class Subscribe  extends Utils {};
//  發佈者
class Publish extends Utils {};
//  中轉站
class Broker {
  constructor(){
    this.publish = new Publish();
    this.subscribe = new Subscribe();
  }
  //  訂閱
  Subscribe(fn,key){
    this.subscribe.Add(fn,key);
  }
  //  發佈
  Release(fn,key){
    this.publish.Add(fn,key);
  }
  Run(key = "any"){
    let publishList = this.publish.observerList;
    let subscribeList = this.subscribe.observerList;
    if(!publishList[key] || !subscribeList[key]) throw "No subscribers or published messages";
    let pub = publishList[key];
    let sub = subscribeList[key];
    let arr = [...pub,...sub];
    while(arr.length){
      let item = arr.shift();
      item(key);
    }
  }
}
class Employees {
  constructor(name){
    this.name = name;
  }
  getName(){
    let {name} = this;
    return name;
  }
  receivedMessage(key,name){
    console.log(`${name}收到了${key}發來的消息`);
  }
}
class Public {
  constructor(name){
    this.name = name;
  }
  getName(){
    let {name} = this;
    return name;
  }
  sendMessage(key){
    console.log(`${key}發送了一條消息`);
  }
}
let broker = new Broker();
let SundayPublic = new Public("Sunday");
let MayPublic = new Public("May");
let Angie = new Employees("Angie");
let Aaron = new Employees("Aaron");
broker.Subscribe(() => {
  Angie.receivedMessage(SundayPublic.getName(),Angie.getName());
},SundayPublic.getName());
broker.Subscribe(() => {
  Angie.receivedMessage(SundayPublic.getName(),Aaron.getName());
},SundayPublic.getName());
broker.Subscribe(() => {
  Aaron.receivedMessage(MayPublic.getName(),Aaron.getName());
},MayPublic.getName());
broker.Release(MayPublic.sendMessage,MayPublic.getName());
broker.Release(SundayPublic.sendMessage,SundayPublic.getName());
broker.Run(SundayPublic.getName());
broker.Run(MayPublic.getName());
//  Sunday發送了一條消息
//  Angie收到了Sunday發來的消息
//  Aaron收到了Sunday發來的消息
//  May發送了一條消息
//  Aaron收到了May發來的消息

經過上面的輸出結果能夠得出,只要訂閱過該公衆號的用戶,只要公衆號發送一條消息,全部訂閱過該條消息的用戶都是能夠收到這條消息。雖然代碼有點多,可是確確實實可以體現發佈訂閱模式的魅力,很不錯。

優勢
  1. 支持簡單的廣播通訊,當對象狀態發生改變時,會自動通知已經訂閱過的對象。
  2. 發佈者與訂閱者耦合性下降,發佈者只管發佈一條消息出去,它不關心這條消息如何被訂閱者使用,同時,訂閱者只監聽發佈者的事件名,只要發佈者的事件名不變,它無論發佈者如何改變;同理賣家(發佈者)它只須要將鞋子來貨的這件事告訴訂閱者(買家),他無論買家到底買仍是不買,仍是買其餘賣家的。只要鞋子到貨了就通知訂閱者便可。
缺點
  1. 建立訂閱者須要消耗必定的時間和內存。
  2. 雖然能夠弱化對象之間的聯繫,若是過分使用的話,反而使代碼很差理解及代碼很差維護。
小結

發佈訂閱模式能夠下降發佈者與訂閱者之間的耦合程度,二者之間歷來不關係你是誰,你要做什麼?訂閱者只須要跟隨發佈者,若發佈者發生變化就會通知訂閱者應該也作出相對於的變化。發佈者與訂閱者之間不存在直接通訊,他們全部的一切事情都是經過中介者相互通訊,它過濾全部傳入的消息並相應地分發它們。發佈訂閱模式可用於在不一樣系統組件之間傳遞消息的模式,而這些組件不知道關於彼此身份的任何信息。

觀察者模式與發佈訂閱的區別

  1. Observer模式中,Observers知道Subject,同時Subject還保留了Observers的記錄。然而,在發佈者/訂閱者中,發佈者和訂閱者不須要彼此瞭解。他們只是在消息隊列或代理的幫助下進行通訊。
  2. Publisher / Subscriber模式中,組件是鬆散耦合的,而不是Observer模式。
  3. 觀察者模式主要以同步方式實現,即當某些事件發生時,Subject調用其全部觀察者的適當方法。發佈者/訂閱者在大多狀況下是異步方式(使用消息隊列)。
  4. 觀察者模式須要在單個應用程序地址空間中實現。另外一方面,發佈者/訂閱者模式更像是跨應用程序模式。

810680-20181110141219325-989417119.png

圖片源於網絡,侵權必刪

若是以結構來分辨模式,發佈訂閱模式相比觀察者模式多了一箇中間件訂閱器,因此發佈訂閱模式是不一樣於觀察者模式的。若是以意圖來分辨模式,他們都是實現了對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知,並自動更新,那麼他們就是同一種模式,發佈訂閱模式是在觀察者模式的基礎上作的優化升級。在觀察者模式中,觀察者須要直接訂閱目標事件。在目標發出內容改變的事件後,直接接收事件並做出響應。發佈訂閱模式相比觀察者模式多了個事件通道,訂閱者和發佈者不是直接關聯的。目標和觀察者是直接聯繫在一塊兒的。觀察者把自身添加到了目標對象中,可見和發佈訂閱模式差異仍是很大的。在這種模式下,目標更像一個發佈者,他讓添加進來的全部觀察者都執行了傳入的函數,而觀察者就像一個訂閱者。雖然兩種模式都存在訂閱者和發佈者(具體觀察者可認爲是訂閱者、具體目標可認爲是發佈者),可是觀察者模式是由具體目標調度的,而發佈/訂閱模式是統一由調度中心調的,因此觀察者模式的訂閱者與發佈者之間是存在依賴的,而發佈/訂閱模式則不會。

總結

雖然在學習這兩種模式的時候有不少的坎坷,最終仍是按照本身的理解寫出來了兩個案例。或許理解的有誤差,若是哪裏有問題,但願你們在下面留言指正,我會盡快作出修復的。

若是你和我同樣喜歡前端的話,能夠加Qq羣:135170291,期待你們的加入。

相關文章
相關標籤/搜索