閱讀820 返回首頁    go 阿裏雲 go 技術社區[雲棲]


設計模式之三:觀察者模式

  • 觀察者模式
  • 目錄介紹
  • 1.觀察者模式介紹
  • 2.觀察者使用場景
  • 3.觀察者UML圖解
  • 4.觀察者模式簡單實現
  • 4.0 舉個例子
  • 4.1 觀察者代碼
  • 4.2 被觀察者代碼
  • 4.3 測試代碼
  • 4.4 思考
  • 5.觀察者模式Android源碼分析
  • 5.1 先來看看源代碼
  • 5.2 觀察者從哪裏來的,查看setAdapter源代碼
  • 5.3 觀察者在哪裏創建的呢?如何運作
  • 5.4 代碼分析
  • 6.觀察者模式深入探索
  • 7.EventBus事件總線
  • 7.1 遇到的問題
  • 7.2 目前流行事件總線框架
  • 7.3 源碼解讀,單獨寫成博客呢
  • 7.4 使用步驟
  • 8.其他說明
  • 8.1 參考文檔:源碼設計模式解析

0.本人寫的綜合案例

1.觀察者模式介紹

  • 1.1 最常用的地方是GUI係統、訂閱——發布係統等
  • 1.2 重要作用就是解耦,使得它們之間的依賴性更小,甚至做到毫無依賴
  • 1.3 觀察者模式又被稱作發布/訂閱模式,觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
  • 1.4 舉個例子: 就拿csdn這個類似於博客的網站來說吧,如果你訂閱了或者說關注了一個領域,就能收到這個領域文章的推送,如果沒有關注,則不能。是相當於有一個總控製台(被觀察者,持有數據源,這裏的數據源是我們每個訂閱了的人)通知下麵的觀察者。

2.觀察者使用場景

  • 事件多級觸發場景
  • 跨係統的消息交換場景,如消息隊列,事件總線的處理機製

3.觀察者UML圖解

  • 3.1 關於UML類圖 Image.png
  • 3.2 角色介紹

    • 抽象主題(Subject):它把所有觀察者對象的引用保存到一個聚集裏,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。
    • 具體主題(ConcreteSubject):將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。
    • 抽象觀察者(Observer):為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
    • 具體觀察者(ConcreteObserver):實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題狀態協調。
  • 3.3 其他說明

    • 1.Subject 和 Observer 是一個一對多的關係,也就是說觀察者隻要實現 Observer 接口並把自己注冊到 Subject 中就能夠接收到消息事件;
    • 2.Java API有內置的觀察者模式類:java.util.Observable 類和 java.util.Observer 接口,這分別對應著 Subject 和 Observer 的角色;
    • 3.使用 Java API 的觀察者模式類,需要注意的是被觀察者在調用 notifyObservers() 函數通知觀察者之前一定要調用 setChanged() 函數,要不然觀察者無法接到通知;
    • 4.使用 Java API 的缺點也很明顯,由於 Observable 是一個類,java 隻允許單繼承的缺點就導致你如果同時想要獲取另一個父類的屬性時,你隻能選擇適配器模式或者是內部類的方式,而且由於 setChanged() 函數為 protected 屬性,所以你除非繼承 Observable 類,否則你根本無法使用該類的屬性,這也違背了設計模式的原則:多用組合,少用繼承。

4.觀察者模式簡單實現

  • 4.0 舉個例子
    • 就拿csdn這個類似於博客的網站來說吧,如果你訂閱了或者說關注了一個領域,就能收到這個領域文章的推送,如果沒有關注,則不能。是相當於有一個總控製台(被觀察者,持有數據源,這裏的數據源是我們每個訂閱了的人)通知下麵的觀察者。
  • 4.1 觀察者代碼

    • 觀察者,也就是你【程序員】,訂閱專題的人 ``` public class MeObserver implements Observer {

    private String yourName;
    public MeObserver(String yourName){
    this.yourName=yourName;
    }
    @Override
    public void update(Observable o, Object arg) {
    System.out.println("你訂閱的"+arg.toString()+"更新了。");
    }
    @Override
    public String toString() {
    return "your name "+yourName;
    }
    }

  • 4.2 被觀察者代碼

    • 你訂閱的簡書android領域。被觀察者:當他有更新時,所有的觀察者都會接收到響應的通知 ``` public class MeUser extends Observable {

    private String name;
    private int age;
    private String sex;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    setChanged();
    notifyObservers();
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    setChanged();
    notifyObservers();
    }

    public String getSex() {
    return sex;
    }

    public void setSex(String sex) {
    this.sex = sex;
    //setChanged();告知數據改變,通過notifyObservers();發送信號通知觀察者。
    setChanged();
    notifyObservers();
    }

    @Override
    public String toString() {
    return "MeUser [name=" + name + ", age=" + age + ", sex=" + sex + "]";
    }
    }

  • 4.3 測試代碼

    • 相當於服務器更新通知 MeUser user=new MeUser(); MeObserver coder1=new MeObserver("name1"); MeObserver coder2=new MeObserver("name2"); MeObserver coder3=new MeObserver("name3"); user.addObserver(coder1); user.addObserver(coder2); user.addObserver(coder3); user.postNewContentToCoder("contentChanged");
  • 4.4 思考:為什麼觀察者是實現一個observer接口,而被觀察者是繼承一個抽象類呢

    • 被觀察者寫成抽象類的原因是複用,觀察者寫成接口的原因是降低代碼的耦合度,麵向接口編程,在原則裏就是依賴倒置原則,我們倒著思考,如果這裏不是接口,而是一個具體的類,那麼,耦合度就相當高了,如果不是觀察者注冊就無法添加到observable裏,就要修改observable的代碼

5.觀察者模式Android源碼分析

  • RecycleView是Android中重要控件,其中adapter刷新數據的adapter.notifyDataSetChanged()就用到了觀察者模式
  • 5.1 先來看看源代碼 /* * Notify any registered observers that the data set has changed. * 通知已登記的數據集已更改的任何觀察者。 * * <p>This event does not specify what about the data set has changed, forcing * any observers to assume that all existing items and structure may no longer be valid. * LayoutManagers will be forced to fully rebind and relayout all visible views.</p> * 此事件沒有指定數據集發生了什麼變化,迫使任何觀察者假設所有現有的項和結構可能不再有效。 * LayoutManagers將不得不完全重新綁定和保護所有可見的視圖 */ public final void notifyDataSetChanged() { mObservable.notifyChanged(); } //然後調用此方法 public void notifyChanged() { // 遍曆所有觀察者,並且調用它們的onChanged方法 for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } }
  • 5.2 觀察者從哪裏來的,查看setAdapter源代碼

    public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    requestLayout();
    }
    private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,boolean removeAndRecycleViews) {
    //如果有adapter,那麼先注銷對應觀察者
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        removeAndRecycleViews();
    }
    //重置
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {//將觀察者注冊到adapter中
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    markKnownViewsInvalid();
    }
    
  • **5.3 觀察者在哪裏創建的呢?如何運作

    //查看源代碼,可以知道
    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    //查看onchang方法
    private class RecyclerViewDataObserver extends AdapterDataObserver {
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;
        setDataSetChangedAfterLayout();
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }
    }
    void setDataSetChangedAfterLayout() {
    if (mDataSetHasChangedAfterLayout) {
        return;
    }
    mDataSetHasChangedAfterLayout = true;
    //獲取adapter中數據的數量
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
    holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
        }
    }
    
    mRecycler.setAdapterPositionsAsUnknown();
    // immediately mark all views as invalid, so prefetched views can be
    // differentiated from views bound to previous data set - both in children, and cache
    markKnownViewsInvalid();
    }
    
  • 5.4 代碼分析

  • 在Adapter裏麵有一個AdapterDataObservable,是被觀察者,被觀察者必須有三個方法,注冊,銷毀,通知,這裏的注冊就是registerAdapterDataObserver,通知就是notify相關的。

  • 在setAdapter的時候,將觀察者,也就是RecyclerViewDataObserver注冊到AdapterDataObservable裏麵來維護,觀察者裏麵自然是更新布局。

  • 我們調用notifyDataSetChanged其實就是調用被觀察者的notify相關方法

6.觀察者模式深入探索

7.EventBus事件總線

  • 7.1 遇到的問題
    • 在Activity與Fragment中通信,需要有對方的引用,會導致耦合性較高。怎麼辦呢?
    • 想在Activity-B中回調Activity-A的某個函數,但是Activity又不能手動創建對象設置一個listener。該怎麼辦呢?
    • 如何在service中更新Activity或者fragment的界麵呢???
  • 7.2 目前流行事件總線框架
    • EventBus 是異步執行 使用name pattern模式,效率高,但使用不方便
    • Otto 訂閱函數不是異步執行 使用注解,使用方便,但效率比不上EventBus
    • AndroidEventBus 是異步執行 訂閱函數支持tag,使得事件投遞更準確
  • 7.3 源碼解讀
    • 可以直接看EventBus源碼解析文章
  • 7.4 使用步驟
    • 可以直接看EventBus源碼解析文章

其他說明

最後更新:2017-11-14 20:03:56

  上一篇:go  大家都在提意見,再不提你就OUT了!
  下一篇:go  E-MapReduce Best Practices