1. 程式人生 > >【pattern】設計模式(3) - Observer觀察者模式

【pattern】設計模式(3) - Observer觀察者模式

獨立 使用 數據 技術 很多 調用 edi 基於 ace

源碼地址:https://github.com/vergilyn/design-patterns
另外一個大神很全的Github:https://github.com/iluwatar/java-design-patterns

1. 概述

  觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。當這個主題對象在狀態發生變化時,可以通知所有觀察者對象,使它們(觀察者對象)能夠自動更新自己。
  比如,微信用戶就是觀察者,微信公眾號就是被觀察者(主題對象),當公眾號更新時會通知所有的觀察者。
  以上示例更形象的表述可能是:發布/訂閱(publish/subscribe)。
  (個人覺得)觀察者模式的核心:其實就是觸發及響應。所以感覺跟"責任鏈模式"有一定程度的類似,可能"責任鏈模式"的適用場景更加具體,而"觀察者模式"相對就抽象很多。

  

2. 適用場景

  1. 關聯行為場景,需要註意的是,關聯行為是可拆分的,而不是“組合”關系。
    即一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。

  2. 事件多級觸發場景。
    即一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。

  3. 跨系統的消息交換場景,如消息隊列、事件總線的處理機制。
    (事件總線)一個對象必須通知其他對象,而並不知道這些對象是誰;
    需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。

  這些是我在網上亂七八糟看到的適用場景,能理解的很好理解,不能理解的很頭疼。比如在看某些源碼時,感覺像是observer-pattern,但又感覺不是...

  

3. 類圖

技術分享圖片

涉及到角色:

  • AbstractSubject:抽象主題;
  • Subject:具體主題(被觀察者);重點,會用一個集合List|Map|Set來保存所有的觀察者。
  • AbstractObserver:抽象觀察者;
  • Observer:具體觀察者;

  註意:觀察者observer與被觀察者subject是相對的,某些情況下可能兩者都是。

  之所以要定義AbstractSubject、 AbstractObserver,主要在於對開發原則(SOLID)的考慮,解除耦合及方便擴展。
  (看到有blog總結說Observer滿足"開閉原則",我對SOLID理解不是那麽深,只是有體會應該要依賴抽象/接口,所以才會定義AbstractSubject、 AbstractObserver)。

  特別:因為我定義的是interface AbstractSubject,所以我把observers定義到了Subject。視實際情況可能會定義為abstract class AbstractSubject,然後把observers定義在其中,所有AbstractSubject的子類共享observers.
  

4. 最基本的示例代碼

  場景: subject[微信公眾號],observer[微信用戶]。當公眾號更新時,將更新信息[message]通知所有的觀察者。
  特別:observer-pattern可以分為"推模型"、"拉模型",示例代碼是"推模型"。

/** 抽象主題 */
public interface AbstractSubject {
    String name();
    void add(AbstractObserver observer);
    void del(AbstractObserver observer);
    void publishMessage(String message);
}
/** 抽象觀察者 */
public interface AbstractObserver {
    String name();
    void onMessage(AbstractSubject subject, String msg);
}
/** 具體主題(被觀察者)*/
public class Subject implements AbstractSubject {
    private final Set<AbstractObserver> observers = Sets.newLinkedHashSet();
    private final String name;

    public Subject(String name) {
        this.name = name;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public void add(AbstractObserver observer) {
        observers.add(observer);
    }

    @Override
    public void del(AbstractObserver observer) {
        observers.remove(observer);
    }

    @Override
    public void publishMessage(String message) {
        for (AbstractObserver observer : observers){
            observer.onMessage(this, message);
        }
    }

}
/** 具體觀察者 */
public class Observer implements AbstractObserver {
    private final String name;

    public Observer(String name) {
        this.name = name;
    }

    @Override
    public String name() {
        return name;
    }

    public void onMessage(AbstractSubject subject, String msg) {
        System.out.println(String.format("observer[%s]: subject[%s], message[%s]", name, subject.name(), msg));
    }
}
public class TestObserver {
    public static void main(String[] args) {
        AbstractSubject subject = new Subject("公眾號-01");

        AbstractObserver observer1 = new Observer("user-01");
        AbstractObserver observer2 = new Observer("user-02");
        AbstractObserver observer3 = new Observer("user-03");

        subject.add(observer1);
        subject.add(observer2);
        subject.add(observer3);

        subject.publishMessage("some message");
    }
}
輸出結果:
observer[user-01]: subject[公眾號-01], message[some message]
observer[user-02]: subject[公眾號-01], message[some message]
observer[user-03]: subject[公眾號-01], message[some message]

  

5. 註意事項

  1. (開發效率)雖然observer-pattern代碼看起來很簡單,但如果用於實際業務邏輯代碼,非框架級代碼,用observer-pattern會讓開發、調試、閱讀等內容變得比較復雜。

  2. (運行效率)如果一個subject有很多直接和間接的observer的話,將所有的observers都通知到會花費很多時間。
    解決:
      視具體情況,更多可能采用異步的方式通知各個observer。避免observer之間相互的影響。
      且要註意若通知某個observer發生exception時,subject要怎麽處理?是由subject處理還是由observer處理?

  3. (循環依賴)如果在observer和subject之間有循環依賴的話,註意避免循環調用,否則會導致系統崩潰。

  4. (缺點)observer-pattern沒有相應的機制讓observer知道subject是怎麽發生變化的,而僅僅只是知道subject發生了變化。
      個人並不理解,感覺有辦法能讓object知道subject是怎麽發生變化的啊。

  5. 抉擇:是用"推模式",還是"拉模式"。

6. "推模型"、"拉模型"

個人認同的"推模型"、"拉模型"說法:

  • "推模型":subject維護一份observers的列表,每當有更新發生,subject會把需要的參數主動推送到各個observer。

  • "拉模型": observer維護自己所關心的subjects列表,自行決定在合適的時間(延時性)去subjects獲取相應的更新數據。

  我最初也沒扯清楚"推模型"、"拉模型"的區別。然後去看了幾篇blog,發現有兩種說法,有種說法跟我最初理解的一樣,但當我看見另外一種說法時,我發現更能接受"另外"的這種說法。
  "推模型"、"拉模型"的區別是:如果subject是主動的一方,則是"推模型";反之,若observer是主動方,則是"拉模型"。
  "推模型",如上面代碼示例,是由subject維護了一份observers,由subject主動把消息推送給observer。
  而如果是"拉模型",則是反過來,由observer維護一份subjects,並且是有observer定時去獲取subject的信息。
  
(兩種說法的參考blog)  

  1. 個人認為:錯誤的理解
    參考:http://blog.csdn.net/happyever2012/article/details/44678595
      這邊文章中所的"推"、"拉"跟我最初理解的一樣, 但我一直感覺他們實質都是一樣的。
    當subject通知observer時,不管傳需求的部分參數,還是把subject本身傳遞給observer,感覺都像"推"。所以,我就繼續在網上找別的blog,找到了我認同的說法。

  2. 個人認為:正確的理解
    參考:http://raychase.iteye.com/blog/1337015

6.1. "推模型"、"拉模型"的各自特點

"推模型"的特點:
  1) 精準。如果沒有更新發生,不會有任何更新消息推送的動作,即每次消息推送都發生在確確實實的更新事件之後。
  2) 實時。事件發生後的第一時間即可觸發通知操作。
  3) 可以表達出不同事件發生的先後順序。保證了是按事件改變順序通知observer。
  4) 因為發起方是subject,所以要在subject方指定通知的時間,避開subject自身或observer方的繁忙時段。

"拉模型"的特點:
  1) 延時性。因為是定時去請求subject的信息,所以有延時性。
  2) (不知道怎麽描述)同樣因為定時,如果長時間subject沒變化,observer還是會按時去更新subject,做是否更新判斷,無意義的請求獲取subject。
  3) 因為發起方是observer,所以要在observer方指定更新的時間,避開observer自身或subject方的繁忙時段。
  
  這裏先要明白一個概念,subject一般被認為是服務端,observer則是客戶端。所以有些可能subject與observer根本不是同一個服務(不在同一套代碼內)。
  所以,如果需由observer自己控制自己的更新時間,那可以考慮選擇"拉模型"。反之,若subject覺得這時間段subject服務器壓力太大,又不是一定要馬上通知各個observer,則"推模式"更容易在subject控制更新。

6.2. "推模型"、"拉模型"註意事項

  1) 均無法保證事件都會響應
    "推模型"中,若當subject通知observer時,某個observer客戶端已經崩潰,那麽要考慮subject服務端是不是要記錄這次失敗,重新通知這個observer直到成功。
    "拉模型"中,因為延時性,subject快速的狀態改變1 -> 2 -> 3,observer定時獲取的值是3,那麽要考慮observer是不是要獲取完整的狀態變化過程執行相應的邏輯代碼。
    
  2) "權限控制"交給誰?
    (這可能無關"推模型"、"拉模型") 比如,"推模式"subject會把所有的觸發事件都通知給全部observers,至於這個事件各個observer是不是需要處理,各個observer自己去考慮。(當然,代碼完全可以寫成是在服務端subject控制某個事件不通知某些observer)。
    可能類似的場景如,subject服務端提供了很多免費的服務,observer客戶端要用哪些自己考慮。subject服務端提供了一些收費服務,此時不管是"推模型"還是"拉模型"肯定都只能在subject服務端控制這個權限。

  3) 壓力交給誰去承受?
    比如,若observers數據量相當龐大,subject維護這份數據壓力很大(或全部通知到壓力很大),則可以把關系解脫到observer去完成,即選擇"拉模型"。
    又或者,subject服務端覺得被一堆observer定時請求壓力較大,則選擇"推模型"。
    
  以上很多都可能是我多此一舉的瞎考慮, 其實這點性能差別根本不用考慮。
  暫時感覺比較明顯的一點,是不是要保證實時性。
  實際中我看的較多都是"推模型","拉模型"少很多。比較明顯的"拉模型"就是互聯網頁面的訪問,

7. 擴展: 觀察者模式 vs 責任鏈模式

  暫時還沒有仔細看"責任鏈模式",而且只是個人感覺"責任鏈模式"和"觀察者模式"很相似,都是基於"因為某個東西被觸發,所以需要執行操作"。只是"責任鏈模式"更具體,"觀察者模式"的使用場景更抽象。
參考:
  1. https://www.cnblogs.com/cbf4life/archive/2009/12/29/1634773.html
    以上blog並沒有讓我明白他們之間的區別。
  2. http://bbs.csdn.net/topics/330020444
    可以參考,最後提到的"推模型"與"拉模型"比較有意義。但其余的回答我還是沒理解。

8. 實際場景: Redisson源碼中的observer-pattern(publish/subscribe)

  場景簡述:redisson是用redis實現分布式並發鎖,當多個線程競爭同一把鎖時,只會有一個線程會獲得鎖,此時其余線程都在等待獲取鎖。當持有鎖線程執行完並釋放鎖時,需要通知其余線程去競爭獲取鎖。此時就是可以用publish/subscribe。
  線程A先去嘗試獲取鎖,發現已被其它線程X持有,此時線程A就subscribe競爭該鎖的subject。當線程X釋放鎖時,及時publish通知所有的observer去競爭鎖。(實際redisson中並沒有通知全部的observer,而是只通知隊列中的第一個競爭線程,讓其獲得鎖。)
  
  本來打算把redisson的相關源碼貼出來,但略多,而且要解釋的太多了。等寫redisson源碼閱讀再說吧。

【pattern】設計模式(3) - Observer觀察者模式