Android的設計模式-觀察者模式

前言

Android的設計模式系列文章介紹,歡迎關注,持續更新中:

Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式

1.定義

定義對象間的一種一個對多的依賴關系,當一個對象的狀態發送改變時,所以依賴于它的對象都得到通知并被自動更新。

2.介紹

  • 觀察者屬于行為型模式。
  • 觀察者模式又被稱作發布/訂閱模式。
  • 觀察者模式主要用來解耦,將被觀察者和觀察者解耦,讓他們之間沒有沒有依賴或者依賴關系很小。

3.UML類圖

觀察者模式UML類圖 .jpg
角色說明:
  • Subject(抽象主題):又叫抽象被觀察者,把所有觀察者對象的引用保存到一個集合里,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。
  • ConcreteSubject(具體主題):又叫具體被觀察者,將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。
  • Observer (抽象觀察者):為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
  • ConcrereObserver(具體觀察者):實現抽象觀察者定義的更新接口,當得到主題更改通知時更新自身的狀態。

4.實現

繼續以送快遞為例,快遞員有時只是把快遞拉到樓下,然后就通知收件人下樓去取快遞。

4.1 創建抽象觀察者

定義一個接到通知的更新方法,即收件人收到通知后的反應:

    public interface Observer {//抽象觀察者
        public void update(String message);//更新方法
    }
4.2 創建具體觀察者

實現抽象觀察者中的方法,這里創建兩個類,一個男孩類和一個女孩類,定義他們收到通知后的反應:

    public class Boy implements Observer {

        private String name;//名字
        public Boy(String name) {
            this.name = name;
        }
        @Override
        public void update(String message) {//男孩的具體反應
            System.out.println(name + ",收到了信息:" + message+"屁顛顛的去取快遞.");
        }
    }

    public class Girl implements Observer {

        private String name;//名字
        public Girl(String name) {
            this.name = name;
        }
        @Override
        public void update(String message) {//女孩的具體反應
            System.out.println(name + ",收到了信息:" + message+"讓男朋友去取快遞~");
        }
    }
4.3 創建抽象主題

即抽象被觀察者,定義添加,刪除,通知等方法:

    public interface  Observable {//抽象被觀察者
         void add(Observer observer);//添加觀察者
 
         void remove(Observer observer);//刪除觀察者
    
         void notify(String message);//通知觀察者
    }
4.4 創建具體主題

即具體被觀察者,也就是快遞員,派送快遞時根據快遞信息來通知收件人讓其來取件:

    public class Postman implements  Observable{//快遞員
        
        private List<Observer> personList = new ArrayList<Observer>();//保存收件人(觀察者)的信息
        @Override
        public void add(Observer observer) {//添加收件人
            personList.add(observer);
        }

        @Override
        public void remove(Observer observer) {//移除收件人
            personList.remove(observer);

        }

        @Override
        public void notify(String message) {//逐一通知收件人(觀察者)
            for (Observer observer : personList) {
                observer.update(message);
            }
        }
    }
4.5 客戶端測試
    public void test(){
        Observable postman=new Postman();
        
        Observer boy1=new Boy("路飛");
        Observer boy2=new Boy("喬巴");
        Observer girl1=new Girl("娜美");

        postman.add(boy1);
        postman.add(boy2);
        postman.add(girl1);
        
        postman.notify("快遞到了,請下樓領取.");
    }
輸出結果:
路飛,收到了信息:快遞到了,請下樓領取.屁顛顛的去取快遞.
喬巴,收到了信息:快遞到了,請下樓領取.屁顛顛的去取快遞.
娜美,收到了信息:快遞到了,請下樓領取.讓男朋友去取快遞~
4.6 說明:

實際上,JDK內部也內置了Observable(抽象被觀察者),Observer(抽象觀察者)這兩個類,我們也可以直接拿來用,其代碼如下:

public interface Observer {//(抽象觀察者
    //只定義了一個update方法
    void update(Observable o, Object arg);
}

public class Observable {//抽象被觀察者
    private boolean changed = false;//定義改變狀態,默認為false
    private final ArrayList<Observer> observers;//定義一個觀察者list

    public Observable() {//構造函數,初始化一個觀察者list來保存觀察者
        observers = new ArrayList<>();
    }
    //添加觀察者,帶同步字段的,所以是線程安全的
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!observers.contains(o)) {
            observers.add(o);
        }
    }

    //刪除觀察者
    public synchronized void deleteObserver(Observer o) {
        observers.remove(o);
    }

    //通知所以觀察者,無參數
    public void notifyObservers() {
        notifyObservers(null);
    }

     //通知所有觀察者,帶參數
    public void notifyObservers(Object arg) {

        Observer[] arrLocal;
        //加synchronized字段,保證多線程下操作沒有問題
        synchronized (this) {
            if (!hasChanged())//這里做了是否發生改變的判斷,是為了防止出現無意義的更新
                return;

            arrLocal = observers.toArray(new Observer[observers.size()]);//ArrayList轉換成一個臨時的數組,這樣就防止了通知,添加,移除同時發生可能導致的異常
            clearChanged();///清除改變狀態,設置為false
        }
        //遍歷逐一通知
        for (int i = arrLocal.length-1; i>=0; i--)
            arrLocal[i].update(this, arg);
    }

    //清楚所有觀察者
    public synchronized void deleteObservers() {
        observers.clear();
    }

    //設置被觀察者為改變狀態,設置為true
    protected synchronized void setChanged() {
        changed = true;
    }

    //清除改變狀態,設置為false
    protected synchronized void clearChanged() {
        changed = false;
    }

    //返回當前的改變狀態
    public synchronized boolean hasChanged() {
        return changed;
    }

    //觀察者數量
    public synchronized int countObservers() {
        return observers.size();
    }
}

5. 應用場景

  • 當一個對象的改變需要通知其它對象改變時,而且它不知道具體有多少個對象有待改變時。
  • 當一個對象必須通知其它對象,而它又不能假定其它對象是誰
  • 跨系統的消息交換場景,如消息隊列、事件總線的處理機制。

6. 優點

  • 解除觀察者與主題之間的耦合。讓耦合的雙方都依賴于抽象,而不是依賴具體。從而使得各自的變化都不會影響另一邊的變化。
  • 易于擴展,對同一主題新增觀察者時無需修改原有代碼。

7. 缺點

  • 依賴關系并未完全解除,抽象主題仍然依賴抽象觀察者。
  • 使用觀察者模式時需要考慮一下開發效率和運行效率的問題,程序中包括一個被觀察者、多個觀察者,開發、調試等內容會比較復雜,而且在Java中消息的通知一般是順序執行,那么一個觀察者卡頓,會影響整體的執行效率,在這種情況下,一般會采用異步實現。
  • 可能會引起多余的數據通知。

8. Android中的源碼分析

8.1 控件中Listener監聽方式

Android中我們遇到的最常用的觀察者模式就是各種控件的監聽,如下:

        Button button = (Button) findViewById(R.id.button);
        //注冊觀察者
        button.setOnClickListener(new View.OnClickListener() {
            //觀察者實現
            @Override
            public void onClick(View arg0) {
                Log.d("test", "Click button ");
            }
        });

上面代碼中,button就是具體的主題,也就是被觀察者;new出來的View.OnClickListenerd對象就是具體的觀察者;OnClickListener實際上就是個接口,也就是抽象觀察者;通過setOnClickListener把觀察者注冊到被觀察者中。

一旦button捕獲的點擊事件,即狀態發生變化的時候,就會通過回調注冊的OnClickListener觀察者的onClick方法會來通知觀察者,Button狀態發生變化。。

相關源碼分析:

    public interface OnClickListener {//抽象觀察者

        void onClick(View v);//只有onClick這個方法
    }

    //注冊觀察者
    public void setOnClickListener(@Nullable View.OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);//設置為可點擊
        }
        getListenerInfo().mOnClickListener = l;//把傳入的 OnClickListener 對象賦值給了 getListenerInfo().mOnClickListener,即mListenerInfo的mOnClickListener持有OnClickListener對象的引用
    }

    ListenerInfo getListenerInfo() {//返回ListenerInfo對象,這里是一個單例模式
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

    public boolean performClick() {//執行點擊事件
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);//執行onClick方法,li.mOnClickListener即OnClickListener對象
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

8.2 Adapter的notifyDataSetChanged()方法

當我們使用ListView時,需要更新數據時我們就會調用AdapternotifyDataSetChanged()方法,那么我們來看看notifyDataSetChanged()的實現原理,這個方法是定義在BaseAdaper中,具體代碼如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
     //數據集被觀察者
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    //注冊觀察者
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }
    //注銷觀察者
    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    //數據集改變時,通知所有觀察者
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
}
    //其他代碼略

由上面的代碼可以看出BaseAdapter實際上就是使用了觀察者模式,BaseAdapter就是具體的被觀察者。接下來看看 mDataSetObservable.notifyChanged()的實現:

//數據集被觀察者
public class DataSetObservable extends Observable<DataSetObserver> {
   
    public void notifyChanged() {
        synchronized(mObservers) {
            //遍歷所有觀察者,并調用他們的onChanged()方法
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }

    //其他代碼略
}

現在我們看到了有觀察者的影子,那么這些觀察者是從哪里來的呢?實際上這些觀察者是在ListView通過setAdaper()設置Adaper時產生的:

public class ListView extends AbsListView {
    //其他代碼略
    
    public void setAdapter(ListAdapter adapter) {
        //如果已存在Adapter,先注銷該Adapter的觀察者
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
        
        //其他代碼略
        
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            mItemCount = mAdapter.getCount();//獲取Adapter中的數據的數量
            checkFocus();

            mDataSetObserver = new AdapterDataSetObserver();//創建一個數據集觀察者
            mAdapter.registerDataSetObserver(mDataSetObserver);//注冊觀察者

           //其他代碼略
        } 
    }
}

從上面的代碼可以看到,觀察者有了,那么這個觀察者主要是干什么的呢?

class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
        @Override
        public void onChanged() {
            super.onChanged();//調用父類的onChanged()方法
            if (mFastScroller != null) {
                mFastScroller.onSectionsChanged();
            }
        }

       //其他代碼略
    }

AdapterDataSetObserver類中的onChanged()方法沒看出啥,繼續看他父類的onChanged()方法:

class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
        //觀察者的核心實現
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();//獲取Adapter中的數據的數量
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //重新布局
            requestLayout();
        }

       //其他代碼略
    }

最終就是在AdapterDataSetObserver這個類里面的onChanged()方法中實現了布局的更新。

簡單總結:

  • ListView的數據發生變化時,我們調用AdapternotifyDataSetChanged()方法,這個方法又會調用所有觀察者(AdapterDataSetObserver)的onChanged()方法,onChanged()方法又會調用requestLayout()方法來重新進行布局。

8.3 BroadcastReceiver

BroadcastReceiver作為Android的四大組件之一,實際上也是一個典型的觀察者模式.通過sendBroadcast發送廣播時,只有注冊了相應的IntentFilterBroadcastReceiver對象才會收到這個廣播信息,其onReceive方法才會被調起.BroadcastReceiver的代碼比較復雜,這里就不展開了.先挖個坑,后面也會出BroadcastReceiver的相關源碼分析.

8.4 其他

另外,一些著名的第三方事件總線庫,比如RxJava、RxAndroid、EventBus、otto等等,也是使用了觀察者模式.有興趣的可以去看下他們的源碼.

相關文章閱讀
Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容