參考
- 《設計模式:可複用面向對象軟件的基礎 》5.7 Observer 觀察者 對象行爲型模式
- 《設計模式解析》 18.4 Observer模式
- 《Android源碼設計模式解析與實戰》第12章 解決,解耦的鑰匙–觀察者模式
本人能力有限,如有明顯錯誤,不規範的地方,請指正,謝謝。
意圖
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。
別名
依賴(Dependents)發佈-訂閱(Publish-Subscribe)
適用場景
- 當一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
- 當對一個對象的改變需要同時改變其它對象,而不知道具體有多少對象有待改變。
- 當一個對象必須通知其它對象,而它又不能確定其它對象是誰。
結構
- subject 抽象主題,也就是被觀察的角色,抽象主題角色把所有觀察者對象的引用保存在一個集合裏,每個主題都可以由任意數量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象。
- ConcreteSubject 具體主題,也就是具體被觀察者角色
- Observer 抽象觀察者,他定義了一個更新接口,使得訂閱的主題更改時更新到自己。
- ConcreteObserver 具體觀察者
優點
- 觀察者與被觀察者之間是抽象耦合,應對業務變化。
- 增強系統靈活性,可擴展性。
缺點
在應用觀察者模式時需要考慮一些開發效率和運行效率問題,程序中包括一個被觀察者,多個觀察則,開發和調式等內容會比較複雜。
例子
Observer模式是直接接觸過的最常見的設計模式之一,GUI程序應用得比較廣。
例子1 程序員訂閱Android博客週刊
描述
開發技術前線網站是一個彙集各種技術文章的網站,它支持郵箱訂閱,一旦有用戶訂閱了它,每當網站出新內容時,會自動將新內容推送到用戶郵箱。
因爲java核心庫裏已經有了Observer抽象觀察者接口和Observable抽象被觀察者類,所以我們直接實現和繼承它們即可擴展我們自己的業務。
簡單代碼實現
/**
* 程序員是訂閱者,就是具體的觀察者
*
* @author newtrekWang
* @email [email protected]
* @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 + '\'' +
'}';
}
}
/**
* 開發技術網站
* @author newtrekWang
* @email [email protected]
* @time 2018/8/24 0:16
*/
public class DevTechFrontier extends Observable {
/**
* 通知所有觀察者
* @param page 新的文章
*/
public void postNewPage(Page page){
// 設置狀態已改變
setChanged();
notifyObservers(page);
}
}
/**
* 文章類
* @author newtrekWang
* @email [email protected]
* @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","技術內容"));
}
執行結果:
conder5 得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術內容'}
conder4 得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術內容'}
conder3 得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術內容'}
conder2 得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術內容'}
conder1 得到了文章:Page{date='Fri Aug 24 00:19:55 CST 2018', author='wang', content='技術內容'}
例子2 BaseAdapter中的notifyDatasetChanged()
我們往ListView添加數據後,都會調用Adapterder的notifyDataChanged()方法刷新顯示數據。其實它就是內置了一個DataSetObservable和多個DataSetObserver,調用notifyDataChanged()會讓DataSetObservable一一通知DataSetObserver的onChanged()回調,然後onChanged()會根據現在的數據情況調用ListView重新佈局方法,刷新UI,這就是一個典型的Observer模式。
例子3 BroadcastReceiver 廣播註冊
參考 BroadcastReceiver中的那些設計模式
BroadcastReceiver是Android的四個組件之一,它作爲應用內,進程間的一種重要通信手段,能夠將某個消息通過廣播的形式傳遞給它註冊的對應廣播接收器的對象。接收對象即BroadcastReceiver,爲觀察者,然後它需要通過Context的registerReceiver方法註冊到AMS中,當通過sendBroadcase發送廣播時,所有註冊了對應的IntentFilter的BroadcastReceiver對象就會接收到這個消息,Broadcast的onReceive方法就會調用,這就是一個典型的發佈–訂閱模式,只是發送廣播時會通過IntentFilter作一些匹配過濾操作。
例子4 RxJava
在Android開發中,我們經常需要在兩個不同的業務場景之間進行通信,比如子線程要發消息給主線程。我們單靠AndroidSDK裏面的API的話,就必須重寫Handler,將主線程中創建的Handler對象傳給子線程,然後子線程通過handler發送消息,主線程維護的消息隊列收到消息,然後消息又交給handler處理,最終完成消息發送。
Android創造的AsyncTask和Handler貌似讓異步代碼做得簡潔,但是業務多了,就不一定了。
反正我覺得自己手寫Handler,懶得寫,經常見到的情況是一個Handler來處理多個子線程發來的消息,不是if-else就是switch case,message還要做一些變換啥的,還要考慮有沒有內存泄漏情況,子線程任務要能及時取消等等,有點煩,除非你設計好了一個良好的封裝。
如果你用過RxJava,保證再也不想用Handler了,因爲RxJava 運用觀察者模式和鏈式操作符解決了上述很多問題。
RxJava 有四個基本概念:Observable (可觀察者,即被觀察者)、 Observer (觀察者)、 subscribe (訂閱)、事件。Observable 和 Observer 通過 subscribe() 方法實現訂閱關係,從而 Observable 可以在需要的時候發出事件來通知 Observer。
使用方法就跟通常的觀察者模式使用差不多,只是通常我們都只是一對一,即一個觀察者對一個被觀察者,具體使用可以看看以下兩位大佬的文章。
給 Android 開發者的 RxJava 詳解
當初第一次知道觀察者模式就是因爲看到這篇文章。
給初學者的RxJava2.0教程 上下游管道例子講得很好。
我不知道一些大廠對這些線程調度用的是什麼方案,不過從網上的大多技術文章來看,網絡業務方面Okhttp3+Retrofit2+Rxjava2是很主流的。
例子5 MVVM 中的View-ViewModel
MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行爲抽象化,讓我們將視圖 UI 和業務邏輯分開。
View-ViewModel簡單來說就是數據與視圖組件建立綁定關係,比如單向綁定
這點微軟的WPF做得很高,WPF天生支持mvvm,只要vm中的數據模型值發生了變化,與之對應的view控件就會自動刷新顯示,不在自己用Controller之類的控制view顯示新數據。
其實原理也就是觀察者模式原理,vm爲被觀察者,v爲觀察者,vm有變化,就會觸發vm通知v更新。
綁定關係
- 單向綁定:vm -> v 或者 v -> vm
- 雙向綁定:vm \<-> v
應用mvvm
- 使用RxBinding
- 使用Android Jetpack Components 的 DataBinding,LiveData
例子6 各種Bus,EventBus,RxBus
EventBus,RxBus主要是可以解決不同組件之間的通信問題,當然不同線程之間也可以。要比Android的廣播好用點,而且不依賴Contenxt。
單片機有總線,Qt有槽函數,貌似Android就沒有,所以有人就造了Android應用中的總線。
我覺得這些總線的特點就是發佈者是唯一的單例,但可以被多個訂閱者訂閱,跟Rxjava常用的一對一模式有點不同。
EnventBus原理 直接上圖
RxBus也是Rxjava的擴展,只是用的是可以一對多的觀察者
最近不是很流行組件化嗎(分業務module那種),業務module之間的通信方案應該肯定優選XXBus.