Android中的設計模式之觀察者模式

參考

  • 《設計模式:可復用面向?qū)ο筌浖幕A(chǔ) 》5.7 Observer 觀察者 對象行為型模式
  • 《設計模式解析》 18.4 Observer模式
  • 《Android源碼設計模式解析與實戰(zhàn)》第12章 解決,解耦的鑰匙--觀察者模式

本人能力有限,如有明顯錯誤,不規(guī)范的地方,請指正,謝謝。

意圖

定義對象間的一種一對多的依賴關(guān)系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并自動更新。

別名

依賴(Dependents)發(fā)布-訂閱(Publish-Subscribe)

適用場景

  • 當一個抽象模型有兩個方面,其中一個方面依賴于另一個方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
  • 當對一個對象的改變需要同時改變其它對象,而不知道具體有多少對象有待改變。
  • 當一個對象必須通知其它對象,而它又不能確定其它對象是誰。

結(jié)構(gòu)

觀察者模式結(jié)構(gòu)
  • subject 抽象主題,也就是被觀察的角色,抽象主題角色把所有觀察者對象的引用保存在一個集合里,每個主題都可以由任意數(shù)量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象。
  • ConcreteSubject 具體主題,也就是具體被觀察者角色
  • Observer 抽象觀察者,他定義了一個更新接口,使得訂閱的主題更改時更新到自己。
  • ConcreteObserver 具體觀察者

優(yōu)點

  • 觀察者與被觀察者之間是抽象耦合,應對業(yè)務變化。
  • 增強系統(tǒng)靈活性,可擴展性。

缺點

在應用觀察者模式時需要考慮一些開發(fā)效率和運行效率問題,程序中包括一個被觀察者,多個觀察則,開發(fā)和調(diào)式等內(nèi)容會比較復雜。

例子

Observer模式是直接接觸過的最常見的設計模式之一,GUI程序應用得比較廣。

例子1 程序員訂閱Android博客周刊

描述

開發(fā)技術(shù)前線網(wǎng)站是一個匯集各種技術(shù)文章的網(wǎng)站,它支持郵箱訂閱,一旦有用戶訂閱了它,每當網(wǎng)站出新內(nèi)容時,會自動將新內(nèi)容推送到用戶郵箱。

因為java核心庫里已經(jīng)有了Observer抽象觀察者接口和Observable抽象被觀察者類,所以我們直接實現(xiàn)和繼承它們即可擴展我們自己的業(yè)務。

簡單代碼實現(xiàn)

/**
 * 程序員是訂閱者,就是具體的觀察者
 *
 * @author newtrekWang
 * @email wangjiaxing20160101@gmail.com
 * @time 2018/8/24  0:07
 */
public class Coder implements Observer {
    /**
     * 名字
     */
    private String name;

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

    @Override
    public void update(Observable o, Object arg) {

        if (arg instanceof Page) {
            System.out.println(name + "  得到了文章:" + arg.toString());
        }
    }

    @Override
    public String toString() {
        return "Coder{" +
                "name='" + name + '\'' +
                '}';
    }
}

/**
 *  開發(fā)技術(shù)網(wǎng)站
 * @author newtrekWang
 * @email  wangjiaxing20160101@gmail.com
 * @time   2018/8/24  0:16
 */
public class DevTechFrontier extends Observable {
    /**
     * 通知所有觀察者
     * @param page 新的文章
     */
    public void postNewPage(Page page){
        // 設置狀態(tài)已改變
        setChanged();
        notifyObservers(page);
    }
}

/**
 *  文章類
 * @author newtrekWang
 * @email  wangjiaxing20160101@gmail.com
 * @time   2018/8/24  0:08
 */
public class Page {
    private String  date;
    private String author;
    private String content;

    public Page(String date, String author, String content) {
        this.date = date;
        this.author = author;
        this.content = content;
    }

    @Override
    public String toString() {
        return "Page{" +
                "date='" + date + '\'' +
                ", author='" + author + '\'' +
                ", content='" + content + '\'' +
                '}';
    }
}
// 測試
public static void main(String[] args){
        DevTechFrontier devTechFrontier = new DevTechFrontier();

        Coder coder1 = new Coder("conder1");
        Coder coder2 = new Coder("conder2");
        Coder coder3 = new Coder("conder3");
        Coder coder4 = new Coder("conder4");
        Coder coder5 = new Coder("conder5");

        devTechFrontier.addObserver(coder1);
        devTechFrontier.addObserver(coder2);
        devTechFrontier.addObserver(coder3);
        devTechFrontier.addObserver(coder4);
        devTechFrontier.addObserver(coder5);

        devTechFrontier.postNewPage(new Page(new Date().toString(),"wang","技術(shù)內(nèi)容"));
    }

執(zhí)行結(jié)果:

conder5  得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術(shù)內(nèi)容'}
conder4  得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術(shù)內(nèi)容'}
conder3  得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術(shù)內(nèi)容'}
conder2  得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術(shù)內(nèi)容'}
conder1  得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術(shù)內(nèi)容'}

例子2 BaseAdapter中的notifyDatasetChanged()

我們往ListView添加數(shù)據(jù)后,都會調(diào)用Adapterder的notifyDataChanged()方法刷新顯示數(shù)據(jù)。其實它就是內(nèi)置了一個DataSetObservable和多個DataSetObserver,調(diào)用notifyDataChanged()會讓DataSetObservable一一通知DataSetObserver的onChanged()回調(diào),然后onChanged()會根據(jù)現(xiàn)在的數(shù)據(jù)情況調(diào)用ListView重新布局方法,刷新UI,這就是一個典型的Observer模式。

例子3 BroadcastReceiver 廣播注冊

參考 BroadcastReceiver中的那些設計模式

BroadcastReceiver是Android的四個組件之一,它作為應用內(nèi),進程間的一種重要通信手段,能夠?qū)⒛硞€消息通過廣播的形式傳遞給它注冊的對應廣播接收器的對象。接收對象即BroadcastReceiver,為觀察者,然后它需要通過Context的registerReceiver方法注冊到AMS中,當通過sendBroadcase發(fā)送廣播時,所有注冊了對應的IntentFilter的BroadcastReceiver對象就會接收到這個消息,Broadcast的onReceive方法就會調(diào)用,這就是一個典型的發(fā)布--訂閱模式,只是發(fā)送廣播時會通過IntentFilter作一些匹配過濾操作。

例子4 RxJava

在Android開發(fā)中,我們經(jīng)常需要在兩個不同的業(yè)務場景之間進行通信,比如子線程要發(fā)消息給主線程。我們單靠AndroidSDK里面的API的話,就必須重寫Handler,將主線程中創(chuàng)建的Handler對象傳給子線程,然后子線程通過handler發(fā)送消息,主線程維護的消息隊列收到消息,然后消息又交給handler處理,最終完成消息發(fā)送。

Android創(chuàng)造的AsyncTask和Handler貌似讓異步代碼做得簡潔,但是業(yè)務多了,就不一定了。
反正我覺得自己手寫Handler,懶得寫,經(jīng)常見到的情況是一個Handler來處理多個子線程發(fā)來的消息,不是if-else就是switch case,message還要做一些變換啥的,還要考慮有沒有內(nèi)存泄漏情況,子線程任務要能及時取消等等,有點煩,除非你設計好了一個良好的封裝。

如果你用過RxJava,保證再也不想用Handler了,因為RxJava 運用觀察者模式和鏈式操作符解決了上述很多問題。

RxJava 有四個基本概念:Observable (可觀察者,即被觀察者)、 Observer (觀察者)、 subscribe (訂閱)、事件。Observable 和 Observer 通過 subscribe() 方法實現(xiàn)訂閱關(guān)系,從而 Observable 可以在需要的時候發(fā)出事件來通知 Observer。

使用方法就跟通常的觀察者模式使用差不多,只是通常我們都只是一對一,即一個觀察者對一個被觀察者,具體使用可以看看以下兩位大佬的文章。

給 Android 開發(fā)者的 RxJava 詳解
當初第一次知道觀察者模式就是因為看到這篇文章。

給初學者的RxJava2.0教程 上下游管道例子講得很好。

我不知道一些大廠對這些線程調(diào)度用的是什么方案,不過從網(wǎng)上的大多技術(shù)文章來看,網(wǎng)絡業(yè)務方面Okhttp3+Retrofit2+Rxjava2是很主流的。

例子5 MVVM 中的View-ViewModel

MVVM是Model-View-ViewModel的簡寫。它本質(zhì)上就是MVC 的改進版。MVVM 就是將其中的View 的狀態(tài)和行為抽象化,讓我們將視圖 UI 和業(yè)務邏輯分開。

View-ViewModel簡單來說就是數(shù)據(jù)與視圖組件建立綁定關(guān)系,比如單向綁定
這點微軟的WPF做得很高,WPF天生支持mvvm,只要vm中的數(shù)據(jù)模型值發(fā)生了變化,與之對應的view控件就會自動刷新顯示,不在自己用Controller之類的控制view顯示新數(shù)據(jù)。

其實原理也就是觀察者模式原理,vm為被觀察者,v為觀察者,vm有變化,就會觸發(fā)vm通知v更新。

綁定關(guān)系

  • 單向綁定:vm -> v 或者 v -> vm
  • 雙向綁定:vm <-> v

應用mvvm

  1. 使用RxBinding
  2. 使用Android Jetpack Components 的 DataBinding,LiveData

例子6 各種Bus,EventBus,RxBus

EventBus,RxBus主要是可以解決不同組件之間的通信問題,當然不同線程之間也可以。要比Android的廣播好用點,而且不依賴Contenxt。

單片機有總線,Qt有槽函數(shù),貌似Android就沒有,所以有人就造了Android應用中的總線。

我覺得這些總線的特點就是發(fā)布者是唯一的單例,但可以被多個訂閱者訂閱,跟Rxjava常用的一對一模式有點不同。

EnventBus原理 直接上圖

EnventBus 結(jié)構(gòu)

RxBus也是Rxjava的擴展,只是用的是可以一對多的觀察者

最近不是很流行組件化嗎(分業(yè)務module那種),業(yè)務module之間的通信方案應該肯定優(yōu)選XXBus.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容