設計模式 ~ 觀察者模式分析與實戰

設計模式系列文章目錄導讀:

設計模式 ~ 面向對象 6 大設計原則剖析與實戰
設計模式 ~ 模板方法模式分析與實戰
設計模式 ~ 觀察者模式分析與實戰
設計模式 ~ 單例模式分析與實戰
設計模式 ~ 深入理解建造者模式與實戰
設計模式 ~ 工廠模式剖析與實戰
設計模式 ~ 適配器模式分析與實戰
設計模式 ~ 裝飾模式探究
設計模式 ~ 深入理解代理模式
設計模式 ~ 小結

今天我們繼續來探討下設計模式中的 觀察者模式,在很多開源框架中也有觀察者模式的應用,如 RxJava, EventBus

除了一些開源框架,Android Framework 源碼中也用到了觀察者模式,如 ListView/RecyclerView, BroadcastReceiver

所以說掌握觀察者模式是很有必要的,下面開始介紹觀察者模式的原理以及在項目的一些實踐

觀察者模式基本概念

觀察者模式(Observer Pattern) 也叫做發佈訂閱模式(Publish/subscribe)

觀察者模式用於定義對象間一對多的依賴關係,使得每當一個對象改變狀態,所有依賴於它的對象都會得到通知並被啓動更新

觀察者模式主要由四個角色組成:

  • 抽象主題(Subject)

    也叫做 被觀察者,可以增加和刪除觀察者對象

  • 抽象觀察者(Observer)

    觀察者收到主題的通知後,進行更新操作,對接收到的信息進行處理

  • 具體主題(Concrete Subject)

    不同的主題(被觀察者)業務邏輯不一樣,在具體的主題中定義自己特定的業務邏輯,同時定義對哪些事件進行通知

  • 具體觀察者(Concrete Observer)

    不同類型的觀察者接收到消息後處理邏輯不一樣,根據需求定義具體觀察者

觀察者模式模式的類圖如下所示:

觀察者模式類圖

觀察者模式的實現與使用場景

通過上面的介紹,我們可以很輕鬆的實現一個觀察者模式:

抽象主題(被觀察者)

public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

抽象觀察者

public interface Observer {
    void update();
}

具體主題

public class ConcreteSubject implements Subject {

    private Vector<Observer> list = new Vector<>();
    
    @Override
    public void attach(Observer observer) {
        list.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        list.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer o : list) {
            o.update();
        }
    }

    // 模擬業務邏輯
    public void doSomething(){
        // 通知觀察者
        notifyObservers();
    }
}

具體觀察者

public class ConcreteObserver implements Observer {
    @Override
    public void update() {
        System.out.println("收到通知,處理相關邏輯...");
    }
}

測試類

public class Client {
    public static void main(String[]args){
        ConcreteSubject subject = new ConcreteSubject();
        subject.attach(new ConcreteObserver());
        subject.doSomething();
    }
}

控制檯輸出:

收到通知,處理相關邏輯...

觀察者模式實現起來還是比較簡單的,主要是理解解決問題的思路,那麼觀察者模式主要是爲了解決哪些方面的問題呢?

  • 一個對象行爲的改變,導致一個或多個對象的行爲改變
  • 事件的多級觸發場景,例如 A 對象的行爲影響 B 對象的行爲,B 對象的行爲影響 C 對象的行爲

一般來說如果一個對象的行爲依賴另一個對象的行爲,那麼當被依賴的對象的行爲發生改變的時候去通知另一個對象就可以了

但是有的時候我們不想直接和另一個對象發生耦合,或者根本就拿不到那個對象,從而無法通知它。

這個時候使用 觀察者模式就能很好的解耦了,因爲觀察者和被觀察者是抽象耦合,被觀察者不認識任何一個具體的觀察者。

JDK 中的觀察者模式

正式由於觀察者模式在開發中使用的非常普遍,JDK 已經爲我們提供了觀察者模式的相關功能

下面我們來看下 JDK 是如何來實現觀察者模式的,我們能從中學到什麼?

主題(被觀察者)

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }
    
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    
    public void notifyObservers(Object arg) {
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    
    // 省略其他代碼...

}

觀察者

public interface Observer {
    void update(Observable o, Object arg);
}

JDK 實現的觀察者模式和我們上面的實現的有些細節上的差別:

  • java.util.Observable 就是一個抽象主題,它並不是抽象類也不是接口

    這樣大大簡化了我們使用觀察模式,我們的主題只需要繼承 Observable 即可,但是這樣就不能繼承其他類了

  • 通過 changed 標記來是判斷是否通知觀察者

  • 如果正在通知觀察者們,新添加的觀察者將不會收到通知

  • 通知觀察者的時候可以傳遞參數

  • 主題添加觀察者的時候防止同一主題重複添加

下面來看下如何使用 JDK 提供的觀察者模式:

// 具體主題
public class ConcreteSubject2 extends Observable {

    // 模擬業務邏輯
    public void doSomething(){
        // changed = true
        setChanged();
        // 通知觀察者
        notifyObservers();
    }

}

// 具體觀察者
public class ConcreteObserver2 implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("收到通知,處理相關邏輯...");
    }
}

// 測試類

public class Client2 {

    public static void main(String[] args) {
        ConcreteSubject2 subject = new ConcreteSubject2();
        subject.addObserver(new ConcreteObserver2());
        subject.doSomething();
    }
}

控制檯輸出:

收到通知,處理相關邏輯...

觀察者模式的缺點

雖然觀察者模式應用的非常廣泛,但是觀察者模式也存在一些缺點:

  • 如果一個主題對應過多的觀察者,通知效率低

    如果一個主題有多個直接或間接的觀察者(訂閱者衆多),通知觀察者需要花費的時間也就越多,會影響通知的效率,可以考慮通過異步的方式來解決,異步處理需要考慮線程安全和隊列的問題

  • 主題的循環依賴,導致程序崩潰

    如果主題之間有循環依賴,這樣就會觸發它們之間的循環調用,導致系統崩潰,使用的時候需要特別注意

  • 濫用觀察者模式,可能導致程序不好維護

    Android 開發中,使用的最多的觀察者模式的框架就是 EventBus 了,但是這也給我們帶了很多問題,如果一個應用中任何地方都可以發通知,開發者並不知道某一個頁面,會有多少個地方會通知它去修改,也不知道發送一個通知,會影響多少個頁面。如果通知滿天飛,使得程序的行爲變得不可控,變得不好維護。雖然 EventBus 的使用非常簡單,但是如果一個程序濫用 EventBus,也會給程序帶來非常多的負面影響

項目實踐

關於觀察者模式在項目中的實踐就太多了,在 Android 開發中 EventBus 用的也比較多,使得我們在使用觀察者模式更加簡單

Android 開發中需要用到的觀察者模式的地方太多,所以才使得 EventBus 的使用非常普遍,所以在這裏就不通過具體的案例來分析觀察者模式的使用了,常見的場景比如:

  • 推送來了,需要刷新頁面
  • 多個頁面依賴某個狀態的變化

總而言之,某個對象的發生變化,需要通知其他對象,但是又拿不到其他對象的引用,或者不適合直接強耦合;抑或是一個對象行爲的改變,導致一個或多個對象的行爲改變,都可以使用 觀察者模式

Reference

  • 《設計模式之禪》
  • 《Java設計模式及實踐》
  • 《Java設計模式深入研究》
  • 《設計模式(Java版)》

如果你覺得本文幫助到你,給我個關注和讚唄!

另外本文涉及到的代碼都在我的 AndroidAll GitHub 倉庫中。該倉庫除了 設計模式,還有 Android 程序員需要掌握的技術棧,如:程序架構、設計模式、性能優化、數據結構算法、Kotlin、Flutter、NDK,以及常用開源框架 Router、RxJava、Glide、LeakCanary、Dagger2、Retrofit、OkHttp、ButterKnife、Router 的原理分析 等,持續更新,歡迎 star。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章