PS:轉載請註明出處
做者: TigerChain
地址: www.jianshu.com/p/b972ba509…
本文出自 TigerChain 簡書 人人都會設計模式java
教程簡介git
正文github
一、警察抓小偷設計模式
在現實生活中,警察抓小偷是一個典型的觀察者模式「這以一個慣犯在街道逛街而後被抓爲例子」,這裏小偷就是被觀察者,各個幹警就是觀察者,幹警時時觀察着小偷,當小偷正在偷東西「就給幹警發送出一條信號,實際上小偷不可能告訴幹警我有偷東西」,幹警收到信號,出擊抓小偷。這就是一個觀察者模式微信
二、裝模做樣寫做業網絡
小時候家裏家活比較多,爸媽讓我去幹活的時候,我偶爾會說我要學習「其實不想去幹活,固然只是偶爾,我仍是經常幹家務的」,而後爸媽就去地裏了,我一我的在家裏,先擺出一張桌子「上面放好想看的書」,而後打開電視機看起電視劇,可是又怕家裏人回到家中看到我在看電視,因而把家裏的大門鎖住「當爸媽回的時候確定要開門」,當我聽見開門聲就會立馬關掉電視,作到做業桌上「裝模做樣寫做業」----在這過程當中:我充當的就是觀察者,爸媽就是被觀察者,他們開門就會觸發門響「至關於告訴我說他們回來了」,我聽到響聲「關電視,寫做業」,有過相似的經驗的朋友們下面點個贊app
三、遠程視頻會議等ide
老闆和員工遠程開會:老闆是被觀察者,員工是觀察者。微信公號:微信公號做者是被觀察者,微信用戶是觀察者「當公號做者發送一篇文章,關注了公號的觀察者均可以收到文章」等函數
觀察者模式的定義佈局
觀察者模式描述的是一種一對多的關係「一個被觀察者對應多個觀察者」,當被觀察者的狀態發生改變時,全部觀察者都會獲得通知。通俗的理解:觀察者模式就是在特定的時刻「被觀察者發送通知」幹特定的事情「觀察者收到通知處理本身相應的事件」
觀察者模式的特色
觀察者模式的三要素:觀察者,被觀察者,事件「訂閱」
觀察者模式的結構
角色 | 類別 | 說明 |
---|---|---|
Subject | 接口或抽象類 | 主題也叫被觀察者 |
RealSubject | 真實的主題類 | 具體的被觀察者,內部維護了觀察者的列表 |
IObserver | 觀察者接口或抽象類 | 抽象出觀察者的接口 |
RealObserver | 具體的觀察者 | 被觀察者有更新,觀察者立馬響應更新 |
觀察者模式簡單的 UML
在舉例以前,咱們先看看一個概念--回調,什麼是回調:就調用一圈又回到自已了「通俗的就能夠這樣認爲」
例子一:小明叫爸爸吃飯
舉個例子,小明媽媽作好了飯,讓小明去地裏叫他爸回來吃飯,小明說好的我立刻去,過了半個小時小明和他爸一塊兒來了,小明給媽媽的說:「媽,爸回來了」,媽媽說:「好的我知道了,讓你爸洗洗手吃飯吧」,在這一過程當中,小明給媽媽的說:「媽,爸回來了」就是一個回調,很差理解?那看代碼吧
小明叫爸爸吃飯簡單的 UML
寫代碼
這就是回調,咱們看看的數據的走向 Mom-->xiaoming-->Mom 轉了一圈回來了,這就是回調
例子二,模擬Android 中 View 的點擊事件
通過例子一,我敢保證多數朋友對回調仍是稀裏糊塗,沒關係,咱們再來一個例子感覺一下,作過 Android 的朋友必定調用過 View.setOnclickListener(OnClickListener onClickListener) 點擊函數,沒錯 OnClickListener 就是一個回調接口,咱們來使用 java 代碼模擬一下這個過程
先看一下 UML
根據 UML 寫代碼
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) ;
}
}複製代碼
/** * Created by TigerChain * 定義一個按鈕 */
public class Button extends View {
public void click(){
super.click();
}
}複製代碼
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();
}
}複製代碼
若是你看 Android 源碼,或是三方的源碼會發現好多這樣的回調方法,好比網絡請求成功失敗的回調等。
使用觀察者模式實現自定義 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 就是抽象的觀察者
/** * Created by TigerChain */
public class Button extends View {
}複製代碼
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();
}
}複製代碼
PS:總結看到了沒,觀察者模式和回調是如此的類似,若是咱們把上面的註冊觀察者改爲一個「和 View 回調如出一轍」,能夠說回調是一種特殊的觀察者模式,回調和觀察者聯繫和區別
有了回調的基礎,下面咱們來看看觀察者模式的幾個精典例子
一、微信公號推送文章
最近看了我文章的人都知道我最近在寫關於設計模式這一系列,在這裏我「TigerChain」就是一個被觀察者,普通的微信用戶就是觀察者,若是微信用戶關注了 TigerChain ,那麼我推送的每一篇的文章,微信用戶就會第一時間收到個人文章「訂閱 TigerChain的用戶」,這就是一個典型的觀察者模式
微信公號推送文章簡單的 UML
根據 UML 擼碼
/** * Created by TigerChain * 定義主題「被觀察者接口」,全部的公號做者共同屬性「其實這裏功能上微信系統的功能,直接抽象成被觀察者」 */
public interface IWxServerSubject {
// 添加觀察者
void attchObserver(IObserver iObserver) ;
// 移除觀察者
void detachObserver(IObserver iObserver) ;
// 通知觀察者
void notifyObserver() ;
}複製代碼
/** * Created by TigerChain * 定義觀察者接口,即關注公號的微信用戶共同屬性 */
public interface Observer {
// 觀察者收到信息,內容爲 info
void reciveContent(String info) ;
}複製代碼
/** * 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();
}
}複製代碼
/** * 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 ;
}
}複製代碼
能夠看到微信用戶有接收推送文章的能力「前提是要關注公號做者」
/** * 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 ,請關注先,謝謝");
}
}
}複製代碼
咱們看到和現實狀況同樣,普通微信用戶關注公號做者,而後做者發送文章,用戶就能夠收到文章了
二、狼王開會
話說冬天來了,狼得找過冬的食物,狼王組織如開了緊急會議,下面的羣狼都看着狼王傳遞會議精神和安排任務,此時狼王就是被觀察者,羣狼就是觀察者,咱們來看看 UML
狼王開會簡單的 UML
根據 UML 擼碼
/** * Created by TigerChain * 抽象的被觀察者 */
public interface Wolf {
// 添加觀察者
void attchObserver(NormalWolf observer) ;
// 移除觀察者
void detchObserver(NormalWolf observer) ;
// 通知觀察者
void notifyObserver(String str) ;
}複製代碼
/** * Created by 抽象的觀察者,普通的狼 */
public abstract class NormalWolf {
// 拿到被觀察者的引用
protected IWolf iWolf ;
/** * 收到狼王下達的命令 * @param str */
public abstract void reciveCommand(String str) ;
}複製代碼
因爲一個狼羣中只有一個狼王,因此狼王是一個單例
/** * 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);
}
}複製代碼
/** * 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);
}
}複製代碼
在這裏咱們實例化一個偵查狼的時候就會把它註冊到被觀察者中,也就是狼王開會的時候,羣狼確定狼羣中的一員「外來狼可不行」,只有內部狼「內部會員」纔有資格開會「這種關係就至關於註冊這個過程」
/** * 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");
}
}複製代碼
/** * 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("一、分工合做,捕獵狼根據偵查狼反饋看機行事 \n" +
"二、偵查狼永遠把危險放在第一位,遇到危險第一時間提醒你們撤退");
}
}複製代碼
狼王下達命令就是發送通知,那麼現場中的狼都會收到通知,典型的觀察者模式
三、自定義 EventBus
在 Android 中咱們經常使用 EventBus,它至關因而一個單例廣播,咱們來自定義一個簡單的 EventBus 「不考慮線程切換」,其實它也是一種觀察者模式「俗稱發佈、訂閱模式」
自定義 EventBus 簡單的 UML
代碼這裏不貼了,我已經上傳到 github 上了,你們能夠自行看看:github.com/githubchen0…
一、RecyclerView 中使用觀察者模式
RecyclerView 中觀察者模式簡單的 UML
源碼就不分析了「貼出代碼估計又得一篇來講」,給出下面流程,你們自行看一下就明白了,動動手印象更深
從 setAdapter 開始看一下觀察者流程
二、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();//和上面的值是同樣的
}
});複製代碼
三、ListView
ListView 中使用觀察者模式和 RecyclerView 相似,你們能夠扒扒這部分源碼,這裏就不說了
優勢
缺點
到此爲止,咱們的觀察者模式就說完了,必定要扒扒 Android 源碼中相應的觀察者模式,你會有一種恍然大悟的感受,點贊是一美德
之後文章會第一時間發在公號,請你們添加博文的公號,掃描添加便可關注
公衆號:TigerChain![]()
TigerChain