PS:轉載請註明出處
作者: TigerChain
地址: www.jianshu.com/p/b972ba509…
本文出自 TigerChain 簡書 人人都會設計模式
教程簡介
- 1、閱讀物件
本篇教程適合新手閱讀,老手直接略過 - 2、教程難度
初級,本人水平有限,文章內容難免會出現問題,如果有問題歡迎指出,謝謝 - 3、Demo 地址:github.com/githubchen0…
正文
一、什麼是觀察者模式
1、生活中的觀察者模式
1、警察抓小偷
在現實生活中,警察抓小偷是一個典型的觀察者模式「這以一個慣犯在街道逛街然後被抓為例子」,這裡小偷就是被觀察者,各個幹警就是觀察者,幹警時時觀察著小偷,當小偷正在偷東西「就給幹警傳送出一條訊號,實際上小偷不可能告訴幹警我有偷東西」,幹警收到訊號,出擊抓小偷。這就是一個觀察者模式
2、裝模作樣寫作業
小時候家裡家活比較多,爸媽讓我去幹活的時候,我偶爾會說我要學習「其實不想去幹活,當然只是偶爾,我還是常常幹家務的」,然後爸媽就去地裡了,我一個人在家裡,先擺出一張桌子「上面放好想看的書」,然後開啟電視機看起電視劇,但是又怕家裡人回到家中看到我在看電視,於是把家裡的大門鎖住「當爸媽回的時候肯定要開門」,當我聽見開門聲就會立馬關掉電視,做到作業桌上「裝模作樣寫作業」----在這過程中:我充當的就是觀察者,爸媽就是被觀察者,他們開門就會觸發門響「相當於告訴我說他們回來了」,我聽到響聲「關電視,寫作業」,有過類似的經驗的朋友們下面點個贊
3、遠端視訊會議等
老闆和員工遠端開會:老闆是被觀察者,員工是觀察者。微信公號:微信公號作者是被觀察者,微信使用者是觀察者「當公號作者傳送一篇文章,關注了公號的觀察者都可以收到文章」等
2、程式中的觀察者模式
觀察者模式的定義
觀察者模式描述的是一種一對多的關係「一個被觀察者對應多個觀察者」,當被觀察者的狀態發生改變時,所有觀察者都會得到通知。通俗的理解:觀察者模式就是在特定的時刻「被觀察者傳送通知」幹特定的事情「觀察者收到通知處理自己相應的事件」
觀察者模式的特點
觀察者模式的三要素:觀察者,被觀察者,事件「訂閱」
觀察者模式的結構
角色 | 類別 | 說明 |
---|---|---|
Subject | 介面或抽象類 | 主題也叫被觀察者 |
RealSubject | 真實的主題類 | 具體的被觀察者,內部維護了觀察者的列表 |
IObserver | 觀察者介面或抽象類 | 抽象出觀察者的介面 |
RealObserver | 具體的觀察者 | 被觀察者有更新,觀察者立馬響應更新 |
觀察者模式簡單的 UML
二、觀察者模式舉例
在舉例之前,我們先看看一個概念--回撥,什麼是回撥:就呼叫一圈又回到自已了「通俗的就可以這樣認為」
1、回撥
例子一:小明叫爸爸吃飯
舉個例子,小明媽媽做好了飯,讓小明去地裡叫他爸回來吃飯,小明說好的我馬上去,過了半個小時小明和他爸一起來了,小明給媽媽的說:“媽,爸回來了”,媽媽說:“好的我知道了,讓你爸洗洗手吃飯吧”,在這一過程中,小明給媽媽的說:“媽,爸回來了”就是一個回撥,不好理解?那看程式碼吧
小明叫爸爸吃飯簡單的 UML
寫程式碼
- 1、定義一個回撥介面
- 2、定義媽媽類
- 3、定義小明類
- 4、測試
- 5、執行檢視結果
這就是回撥,我們看看的資料的走向 Mom-->xiaoming-->Mom 轉了一圈回來了,這就是回撥
例子二,模擬Android 中 View 的點選事件
經過例子一,我敢保證多數朋友對回撥還是稀裡糊塗,不要緊,我們再來一個例子感受一下,做過 Android 的朋友一定呼叫過 View.setOnclickListener(OnClickListener onClickListener) 點選函式,沒錯 OnClickListener 就是一個回撥介面,我們來使用 java 程式碼模擬一下這個過程
先看一下 UML
根據 UML 寫程式碼
- 1、 定義一個 View 類
public class View {
private OnClickListener onClickListener ;
// 觸發點選事件
protected void click(){
if(onClickListener !=null){
onClickListener.onClick(this);
}
}
// 設定回撥
public void setOnClickListener(OnClickListener onClickListener){
this.onClickListener = onClickListener ;
}
public interface OnClickListener{
// 定義回撥方法
void onClick(View v) ;
}
}複製程式碼
- 2、定義一個 Button 類
/**
* Created by TigerChain
* 定義一個按鈕
*/
public class Button extends View {
public void click(){
super.click();
}
}複製程式碼
- 3、測試類 Test
public class Test {
public static void main(String args[]){
Button button = new Button() ;
//看到了沒,看到這裡是不是很親切,是不是發現 次哦! 這就是回撥
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.print("自定義 View 的回撥事件");
}
});
// 模擬使用者點選這個運作,Android 系統的 View 是重寫手勢來呼叫這個方法的,沒有暴露給使用者
button.click();
}
}複製程式碼
- 4、執行檢視結果
如果你看 Android 原始碼,或是三方的原始碼會發現好多這樣的回撥方法,比如網路請求成功失敗的回撥等。
使用觀察者模式實現自定義 View
- 定義 View 「被觀察者」
public class View {
//被觀察者的列表
private ArrayList<OnClickListener> onClickListeners = new ArrayList<>() ;
// 觸發通知
protected void click(){
for(OnClickListener onClickListener:onClickListeners){
if(onClickListener !=null){
onClickListener.onClick(View.this);
}
}
}
// 註冊觀察者
public void setOnClickListener(OnClickListener onClickListener){
onClickListeners.add(onClickListener) ;
}
public interface OnClickListener{
// 定義通知的方法
void onClick(View v) ;
}
public void unRegister(OnClickListener onClickListener){
if(onClickListeners.contains(onClickListener)){
onClickListeners.remove(onClickListener) ;
}
}
}複製程式碼
注意這裡的 OnClickListener 就是抽象的觀察者
- 2、定義一個 Button
/**
* Created by TigerChain
*/
public class Button extends View {
}複製程式碼
- 3、測試 Test
public class Test {
public static void main(String args[]){
//定義一個被觀察者
Button button = new Button() ;
//註冊其中一個觀察者
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("TigerChain");
}
});
// 註冊另一個觀察者
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("never give up");
}
});
// 被觀察者觸發事件
button.click();
}
}複製程式碼
- 4、執行檢視結果
PS:總結看到了沒,觀察者模式和回撥是如此的相似,如果我們把上面的註冊觀察者改成一個「和 View 回撥一模一樣」,可以說回撥是一種特殊的觀察者模式,回撥和觀察者聯絡和區別
- 1、回撥可以說只有一個觀察者,是一對一,是一種特殊的觀察者模式「我是這樣的認為的,個人觀點,如果有誤歡迎指出」
- 2、觀察者被觀察者持有觀察的列表,是一種一對多的關係
- 3、回撥是一種監聽方式,觀察者模式是一種解決方案「設計模式」
有了回撥的基礎,下面我們來看看觀察者模式的幾個精典例子
2、觀察者舉例
1、微信公號推送文章
最近看了我文章的人都知道我最近在寫關於設計模式這一系列,在這裡我「TigerChain」就是一個被觀察者,普通的微信使用者就是觀察者,如果微信使用者關注了 TigerChain ,那麼我推送的每一篇的文章,微信使用者就會第一時間收到我的文章「訂閱 TigerChain的使用者」,這就是一個典型的觀察者模式
微信公號推送文章簡單的 UML
根據 UML 擼碼
- 1、定義抽象的被觀察者 IWxServerSubject.java
/**
* Created by TigerChain
* 定義主題「被觀察者介面」,所有的公號作者共同屬性「其實這裡功能上微信系統的功能,直接抽象成被觀察者」
*/
public interface IWxServerSubject {
// 新增觀察者
void attchObserver(IObserver iObserver) ;
// 移除觀察者
void detachObserver(IObserver iObserver) ;
// 通知觀察者
void notifyObserver() ;
}複製程式碼
- 2、定義抽象的觀察者介面 Observer.Java
/**
* Created by TigerChain
* 定義觀察者介面,即關注公號的微信使用者共同屬性
*/
public interface Observer {
// 觀察者收到資訊,內容為 info
void reciveContent(String info) ;
}複製程式碼
- 3、定義具體的被觀察者「公號作者 TigerChain」 TigerChainSubject.java
/**
* Created by TigerChain
* 定義一個真實的被觀察者 TigerChain「公號的作者」
* 裡面存了訂閱 TigerChain 微信公眾賬號的讀者
*/
public class TigerChainSubject implements IWxServerSubject {
// 訂閱者列表「觀察者列表」,即關注 TigerChain 公號的讀者
private List<IObserver> observers = new ArrayList<>() ;
//作者更新公號的內容
private String updateContent ;
@Override
public void attchObserver(IObserver iObserver) {
observers.add(iObserver) ;
}
@Override
public void detachObserver(IObserver iObserver) {
if(observers.contains(iObserver)) {
observers.remove(iObserver);
}
}
@Override
public void notifyObserver() {
for (IObserver iObserver:observers) {
iObserver.reciveContent(updateContent);
}
}
/**
* 是否關注我的公號
* @param iObserver
* @return
*/
public boolean isAttchObserver(IObserver iObserver){
return observers.contains(iObserver) ;
}
/**
* TigerChain 在公號中釋出文章
* @param updateContent
*/
public void submitContent(String updateContent){
this.updateContent = updateContent ;
this.notifyObserver();
}
}複製程式碼
- 4、定義一個具體的觀察者「普通的微信使用者」 ReaderObserver.java
/**
* Created by TigerChain
* 微信使用者
*/
public class ReaderObserver implements Observer {
// 微信使用者的姓名
private String name ;
public ReaderObserver(String name){
this.uname = name ;
}
@Override
public void reciveContent(String info) {
System.out.println(uname+"注意,TigerChain 傳送了文章---"+info);
}
public String getUname(){
return this.name ;
}
}複製程式碼
可以看到微信使用者有接收推送文章的能力「前提是要關注公號作者」
- 5、來個 Test 類測試一下吧
/**
* Created by TigerChain
* 測試類
*/
public class Test {
public static void main(String args[]){
IWxServerSubject iWxServerSubject = new TigerChainSubject() ;
// 微信使用者
ReaderObserver zhangsai = new ReaderObserver("張三") ;
ReaderObserver lisi = new ReaderObserver("李四") ;
ReaderObserver wangwu = new ReaderObserver("王五") ;
ReaderObserver zhaoLiu = new ReaderObserver("趙六") ;
// 微信使用者張三關注我的公號「即訂閱」
iWxServerSubject.attchObserver(zhangsai);
// 微信使用者李四關注我的公號「即訂閱」
iWxServerSubject.attchObserver(lisi);
// 微信使用者王五關注我的公號「即訂閱」
iWxServerSubject.attchObserver(wangle);
// 我「被觀察者」釋出了一篇文章--觀察者模式
((TigerChainSubject)iWxServerSubject).submitContent("人人都會設計模式:觀察者模式") ;
boolean isAttch = ((TigerChainSubject)iWxServerSubject).isAttchObserver(zhaoLiu) ;
if(!isAttch){
System.out.println(zhaoLiu.getUname()+"你好!你還沒有關注 TigerChain ,請關注先,謝謝");
}
}
}複製程式碼
我們看到和現實情況一樣,普通微信使用者關注公號作者,然後作者傳送文章,使用者就可以收到文章了
- 6、執行檢視結果
2、狼王開會
話說冬天來了,狼得找過冬的食物,狼王組織如開了緊急會議,下面的群狼都看著狼王傳遞會議精神和安排任務,此時狼王就是被觀察者,群狼就是觀察者,我們來看看 UML
狼王開會簡單的 UML
根據 UML 擼碼
- 1、抽象被觀察者功能 Wolf.Java
/**
* Created by TigerChain
* 抽象的被觀察者
*/
public interface Wolf {
// 新增觀察者
void attchObserver(NormalWolf observer) ;
// 移除觀察者
void detchObserver(NormalWolf observer) ;
// 通知觀察者
void notifyObserver(String str) ;
}複製程式碼
- 2、抽象觀察者普通的狼 NormalWolf.java
/**
* Created by 抽象的觀察者,普通的狼
*/
public abstract class NormalWolf {
// 拿到被觀察者的引用
protected IWolf iWolf ;
/**
* 收到狼王下達的命令
* @param str
*/
public abstract void reciveCommand(String str) ;
}複製程式碼
- 3、定義具體的被觀察者狼王 LangWang.java
由於一個狼群中只有一個狼王,所以狼王是一個單例
/**
* Created by TigerChain
* 狼王「被觀察者,下面的狼都看狼王的眼色行事」,是一個單例模式
*/
public class LangWang implements Wolf{
private static LangWang instance ;
private LangWang(){}
public static LangWang getInstance(){
if(instance == null){
synchronized (LangWang.class){
if(instance == null){
instance = new LangWang() ;
}
}
}
return instance ;
}
// 除過狼王外的狼「觀察者」
private List<NormalWolf> observers = new ArrayList<>() ;
// 狼王下達的命令
private String mingLing ;
@Override
public void attchObserver(NormalWolf observer) {
observers.add(observer);
}
@Override
public void detchObserver(NormalWolf observer) {
if(observers.contains(observer)){
observers.remove(observer) ;
}
}
@Override
public void notifyObserver(String str) {
for(NormalWolf observer:observers){
observer.reciveCommand(str);
}
}
/**
* 下達命令
* @param mingLing
*/
public void xiaDaMingling(String mingLing){
this.mingLing = mingLing ;
this.notifyObserver(mingLing);
}
}複製程式碼
- 4、定義一個觀察者偵查狼 ZhenChaLang.java
/**
* Created by TigerChain
* 偵查狼,另一個觀察者
*/
public class ZhenChaLang extends NormalWolf {
public ZhenChaLang(IWolf iWolf){
this.iWolf = iWolf ;
this.iWolf.attchObserver(this);
}
@Override
public void reciveCommand(String string) {
System.out.println("偵查狼:狼王開會傳遞的資訊是 \n"+string);
}
}複製程式碼
在這裡我們例項化一個偵查狼的時候就會把它註冊到被觀察者中,也就是狼王開會的時候,群狼肯定狼群中的一員「外來狼可不行」,只有內部狼「內部會員」才有資格開會「這種關係就相當於註冊這個過程」
- 5、定義另一個觀察者捕獵狼 BuLieLang.java
/**
* Created by TigerChain
* 捕獵狼---觀察者
*/
public class BuLieLang extends NormalWolf {
public BuLieLang(IWolf iWolf){
this.iWolf = iWolf ;
// 新增觀察者,即捕獵狼放在狼王組織中
this.iWolf.attchObserver(this);
}
@Override
public void reciveCommand(String string) {
System.out.println("捕獵狼:狼王開會傳遞的資訊是 \n"+string+"\n");
}
}複製程式碼
- 測試類 Test
/**
* Created by TigerChain
* 測試類
*/
public class Test {
public static void main(String args[]){
// 使用單例模式
LangWang langWang = LangWang.getInstance() ;
BuLieLang buLieLang = new BuLieLang(langWang) ;
ZhenChaLang zhenChaLang = new ZhenChaLang(langWang) ;
// 狼王下達命令就是傳送通知
langWang.xiaDaMingling("1、分工合作,捕獵狼根據偵查狼反饋看機行事 \n" +
"2、偵查狼永遠把危險放在第一位,遇到危險第一時間提醒大家撤退");
}
}複製程式碼
- 6、執行檢視結果
狼王下達命令就是傳送通知,那麼現場中的狼都會收到通知,典型的觀察者模式
3、自定義 EventBus
在 Android 中我們常常使用 EventBus,它相當於是一個單例廣播,我們來自定義一個簡單的 EventBus 「不考慮執行緒切換」,其實它也是一種觀察者模式「俗稱釋出、訂閱模式」
自定義 EventBus 簡單的 UML
程式碼這裡不貼了,我已經上傳到 github 上了,大家可以自行看看:github.com/githubchen0…
三、Android 原始碼中的觀察者模式
1、RecyclerView 中使用觀察者模式
RecyclerView 中觀察者模式簡單的 UML
原始碼就不分析了「貼出程式碼估計又得一篇來說」,給出下面流程,大家自行看一下就明白了,動動手印象更深
從 setAdapter 開始看一下觀察者流程
2、ViewTreeObserver
ViewTreeObserver 是用來監聽檢視樹的觀察者,如果檢視樹發生全域性改變的時候就會收到通知
其中,被觀察者是 ViewTree ,觀察者是 ViewTreeObserver
抽取 ViewTreeObserver 部分程式碼講解
這裡說說 view.getViewTreeObserver().addOnGlobalLayoutListener(xxx) 場景,其它的雷同
public final class ViewTreeObserver {
...
public interface OnGlobalLayoutListener {
public void onGlobalLayout();
}
...
// 新增監聽器
public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
}
mOnGlobalLayoutListeners.add(listener);
}
}
...
// 分發事件相當於傳送通知,即被觀察者呼叫--View
public final void dispatchOnGlobalLayout() {
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}複製程式碼
現在我們有了觀察者 ViewTreeObserver ,觀察者是 ViewTree 我們說了,主要問題的就是 dispatchOnGlobalLayout 誰呼叫了,只有觸發了這個方法那麼事件就回撥回來了「這個方法肯定是被觀察者呼叫了,系統呼叫的」,方法在 ViewRootImpl「關於 ViewRootImpl 可自行去檢視,不在本篇的範圍」 中體現出來了
看看 ViewRootImpl 的部分程式碼
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
private void performTraversals(){
...
// 執行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 執行佈局
performLayout(lp, mWidth, mHeight);
...
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
// 注意看這裡,這裡觸發了 dispatchOnGlobalLayout 方法,系統呼叫
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
...
// 執行繪製
performDraw();
...
}
...
}複製程式碼
看到了沒,mAttachInfo.mTreeObserver.dispatchOnGlobalLayout() 方法是在 ViewRootImpl 中呼叫了「即是 View 呼叫了,只要 View 樹而已發生改變,就會呼叫」,是由系統呼叫的「View 的佈局完成這後,就會呼叫」,並且還呼叫了自定義 View 的測量,佈局,繪製方法。
使用場景:比如我們想在 Activity 的 onCreate() 方法中取得某個 View 的寬高,此時是取不到的,由於佈局還沒有完成載入之前取到的是 0 ,所以使用 view.getViewTreeObserver().addOnGlobalLayoutListener(xxx) 裡面就可以獲取到 view 的寬高了,demo 程式碼如下
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {// 當layout執行結束後回撥
//使用完必須撤銷監聽(只測量一次),否則,會一直不停的不定時的測量,這比較耗效能
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);//Added in API level 16
//view.getViewTreeObserver().removeGlobalOnLayoutListener(this);//廢棄了
int width = view.getMeasuredWidth();
int width2 = view.getWidth();//和上面的值是一樣的
}
});複製程式碼
3、ListView
ListView 中使用觀察者模式和 RecyclerView 類似,大家可以扒扒這部分原始碼,這裡就不說了
四、觀察者模式的優缺點
優點
- 1、解耦,被觀察者只知道觀察者列表「抽象介面」,被觀察者不知道具體的觀察者
- 2、被觀察者傳送通知,所有註冊的觀察者都會收到資訊「可以實現廣播機制」
缺點
- 1、如果觀察者非常多的話,那麼所有的觀察者收到被觀察者傳送的通知會耗時
- 2、觀察者知道被觀察者傳送通知了,但是觀察者不知道所觀察的物件具體是如何發生變化的
- 3、如果被觀察者有迴圈依賴的話,那麼被觀察者傳送通知會使觀察者迴圈呼叫,會導致系統崩潰
到此為止,我們的觀察者模式就說完了,一定要扒扒 Android 原始碼中相應的觀察者模式,你會有一種恍然大悟的感覺,點贊是一美德
以後文章會第一時間發在公號,請大家新增博文的公號,掃描新增即可關注
公眾號:TigerChain