觀察者設(shè)計(jì)模式

1. 定義谎痢?


當(dāng)一個(gè)對象的狀態(tài)發(fā)生改變時(shí),與他相關(guān)聯(lián)的部分對象的狀態(tài)同時(shí)也會(huì)發(fā)生改變。

比如訂閱公眾號(hào):

  • 比如我訂閱了鴻陽的微信公眾號(hào),只要他每更新一篇文章都會(huì)及時(shí)的通知我 [觀察者設(shè)計(jì)模式]渠羞;
    對應(yīng)上邊定義就是:一個(gè)對象的狀態(tài)發(fā)生改變時(shí),就是鴻陽公眾號(hào)有文章更新智哀,由于我訂閱了該公眾號(hào)次询,所以說我的部分狀態(tài)也會(huì)發(fā)生改變,比如我會(huì)去看該公眾號(hào)更新的文章盏触;

EventBus:和觀察者設(shè)計(jì)模式?jīng)]有半毛錢關(guān)系

2. 角色劃分?


被觀察者(Observable):公眾號(hào)块饺;
具體的被觀察者(Concreate Observable):鴻陽公眾號(hào)赞辩;

觀察者(Observer):微信用戶;
具體的觀察者(Concreate Observer):我授艰,Novate

3. 示例代碼 - 訂閱公眾號(hào)辨嗽?


寫一個(gè)事例代碼
被觀察者:WXPublicObservable(公眾號(hào));
具體的被觀察者:WXAdvanceObservable(鴻陽的公眾號(hào))淮腾;

觀察者:IWXUser(微信用戶)糟需;
具體的觀察者:WXUser(Novate、WangZiWen)谷朝;

4. 示例代碼如下


1>:WXPublicObservable洲押,被觀察者 - 微信公眾號(hào)
/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/27 15:03
 * Version 1.0
 * Params:
 * Description:    微信公眾號(hào) - 多個(gè)人去訂閱的公眾號(hào)
*/

public class WXPublicObservable {

    // 所有訂閱用戶的集合
    private List<IWXUser> mWXUsers ;
    public WXPublicObservable(){
        mWXUsers = new ArrayList<>() ;
    }

    /**
     * 訂閱
     */
    public void register(IWXUser wxUser){
        mWXUsers.add(wxUser) ;
    }

    /**
     * 取消訂閱
     */
    public void unregister(IWXUser wxUser){
        mWXUsers.remove(wxUser) ;
    }

    /**
     * 文章更新
     */
    public void update(String article){
        // 推送所有更新的文章
        for (IWXUser wXUser : mWXUsers) {
            wXUser.push(article);
        }
    }
}

2>:WXAdvanceObservable,具體的被觀察者 - 鴻陽的微信公眾號(hào)
/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/27 15:12
 * Version 1.0
 * Params:
 * Description:    具體的被觀察者 - 鴻陽的微信公眾號(hào)
*/

public class WXAdvanceObservable extends WXPublicObservable{

    private String article ;

    public String getArticle() {
        return article;
    }

    public void setArticle(String article) {
        this.article = article;

        // 通知更新圆凰,推送給微信用戶
        update(article);
    }
}

3>:IWXUser杈帐,接口,微信用戶专钉;
/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/27 15:06
 * Version 1.0
 * Params:
 * Description:    微信用戶 - 訂閱該微信公眾號(hào)
*/

public interface IWXUser {

    /**
     * 讀文章
     */
    void push(String article) ;
}

4>:WXUser挑童,具體的觀察者,具體的用戶跃须,訂閱鴻陽微信公眾號(hào)的用戶
/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/27 15:14
 * Version 1.0
 * Params:
 * Description:    具體的用戶 - 訂閱鴻陽的微信公眾號(hào)
*/

public class WXUser implements IWXUser{

    private String name ;

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

    @Override
    public void push(String article) {
        System.out.println(name+"收到了一篇文章:"+article);
    }
}

5>:創(chuàng)建具體的被觀察者和具體的觀察者對象站叼,測試代碼如下:
/**
 * Email: 2185134304@qq.com
 * Created by Novate 2018/5/27 15:16
 * Version 1.0
 * Params:
 * Description:
*/

public class Client {
    public static void main(String[] args){

        // 具體的被觀察者 - 微信公眾號(hào) - 鴻陽的公眾號(hào)
        WXAdvanceObservable wxAdvanceObservable = new WXAdvanceObservable() ;

        // 具體的觀察者 - 微信公眾號(hào) - Novate
        WXUser novate = new WXUser("novate") ;
        WXUser wangziwen = new WXUser("wangziwen") ;

        // 微信公眾號(hào) - 用戶訂閱公眾號(hào)
        wxAdvanceObservable.register(novate);
        wxAdvanceObservable.register(wangziwen);

        // 微信公眾號(hào) - 推送文章
        wxAdvanceObservable.setArticle("《觀察者設(shè)計(jì)模式 - 定義及事例代碼》");

        //  微信公眾號(hào) - 用戶取消訂閱公眾號(hào)
        wxAdvanceObservable.unregister(wangziwen);
    }
}

運(yùn)行結(jié)果打印如下

image

5. 源碼中觀察者設(shè)計(jì)模式的使用場景


1>:RxJava源碼;
2>:ListView的 Adapter的setDataChange的方法菇民;

6. ListView部分源碼分析


1>:ListView中的setAdapter()方法
@Override
    public void setAdapter(ListAdapter adapter) {
        // 防止多次調(diào)用setAdapter尽楔,而不去調(diào)用notifyDataSetChanged
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }
            // 給adapter注冊一個(gè) mDataSetObserver
            mAdapter.registerDataSetObserver(mDataSetObserver);
            // 
            requestLayout();
    }

2>:只要調(diào)用了 adapter.notifyDataSetChanged()方法投储,就會(huì)執(zhí)行下邊代碼:
A:BaseAdapter中的notifyDataSetChanged():
 public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    /**
     * Notifies the attached observers that the underlying data has been changed
     * and any View reflecting the data set should refresh itself.
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }

B:然后調(diào)用
public void notifyChanged() {
        synchronized(mObservers) {

            for (int i = mObservers.size() - 1; i >= 0; i--) {
                // 只要一更新,就會(huì)調(diào)用onChanged()翔试,
                // 所以其實(shí)是用 onChanged()方法把 ListView與adapter進(jìn)行關(guān)聯(lián)
                mObservers.get(i).onChanged();
            }
        }
    }

3>:這個(gè)時(shí)候會(huì)來到AdapterView的onChanged()方法轻要,來更新ListView;
class AdapterDataSetObserver extends DataSetObserver {
        private Parcelable mInstanceState = null;
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            mItemCount = getAdapter().getCount();

            // Detect the case where a cursor that was previously invalidated has
            // been repopulated with new data.
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            // 重新執(zhí)行onMeasure()垦缅、onLayout()冲泥、onDraw()這幾個(gè)方法
            requestLayout();
        }

        @Override
        public void onInvalidated() {
            mDataChanged = true;
            checkFocus();
            requestLayout();
        }

        public void clearSavedState() {
            mInstanceState = null;
        }
    }

7. ListView觀察者設(shè)計(jì)模式圖解


image
由以上分析ListView觀察者設(shè)計(jì)模式圖解可知:

1>:ListView與adapter二者其實(shí)關(guān)聯(lián)不太大壁涎,ListView只是調(diào)用了setAdapter()方法凡恍,那么adapter如果數(shù)據(jù)改變?nèi)绾瓮ㄖ狶istView刷新界面,比如adapter少了一條數(shù)據(jù)怔球,就需要ListView少顯示一條數(shù)據(jù)嚼酝;
2>:其實(shí)在ListView調(diào)用setAdapter()時(shí)候,會(huì)給它的adapter中注冊一群觀察者竟坛,也就是說ListView中有 Observer闽巩,adapter中有一群Observable,也就是說有多個(gè)Observable担汤,把ListView中的Observer注冊到adapter中的Observable涎跨,也就是說把ListView的對象注冊到adapter中的Observable中;
3>:只要調(diào)用了 notifySetDataChanged()崭歧,這個(gè)時(shí)候adapter中所有的 Observable會(huì)進(jìn)行for循環(huán)來調(diào)用 Observer中的onChanged()方法隅很;
4>:然后在AdapterView中,調(diào)用onChanged()方法率碾,然后再調(diào)用 requestLayout()方法叔营,重新執(zhí)行onMeasure()、onLayout()所宰、onDraw()方法绒尊;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仔粥,隨后出現(xiàn)的幾起案子垒酬,更是在濱河造成了極大的恐慌,老刑警劉巖件炉,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勘究,死亡現(xiàn)場離奇詭異,居然都是意外死亡斟冕,警方通過查閱死者的電腦和手機(jī)口糕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來磕蛇,“玉大人景描,你說我怎么就攤上這事十办。” “怎么了超棺?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵向族,是天一觀的道長。 經(jīng)常有香客問我棠绘,道長件相,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任氧苍,我火速辦了婚禮夜矗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘让虐。我一直安慰自己紊撕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布赡突。 她就那樣靜靜地躺著对扶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惭缰。 梳的紋絲不亂的頭發(fā)上浪南,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音从媚,去河邊找鬼逞泄。 笑死患整,一個(gè)胖子當(dāng)著我的面吹牛拜效,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播各谚,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼紧憾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昌渤?” 一聲冷哼從身側(cè)響起赴穗,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎膀息,沒想到半個(gè)月后般眉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡潜支,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年甸赃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冗酿。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡埠对,死狀恐怖络断,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情项玛,我是刑警寧澤貌笨,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站襟沮,受9級(jí)特大地震影響锥惋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜臣嚣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一净刮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧硅则,春花似錦淹父、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至大审,卻和暖如春蘸际,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徒扶。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工粮彤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姜骡。 一個(gè)月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓导坟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親圈澈。 傳聞我的和親對象是個(gè)殘疾皇子惫周,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容