從RecyclerView開始口蝠,研究觀察者模式

Android開發(fā)中一個很常見的場景是有一個列表,列表上每條Item有個點贊的按鈕津坑,當(dāng)你給這條Item點贊的時候妙蔗,這個按鈕就會變成已點贊狀態(tài),相信大家可以腦補到這個場景疆瑰。這里就用到了觀察者模式眉反。

那么什么是觀察者模式,我們稍微瞧一下這個模式的定義穆役,不用太在意寸五,看不懂也沒關(guān)系,我覺得設(shè)計模式是需要用代碼感受的耿币,加這些定義只是為了讓懂的人感受更深梳杏,更系統(tǒng)。
觀察者模式:

觀察者模式

兩個抽象類或者接口,
一個是被觀察者的抽象類或者接口十性,需要持有所有觀察者的引用叛溢,
被觀察者要能做三件事兒:
1.可以注冊觀察者。
2.有注冊就有注銷劲适。
3.可以通知觀察者的方法楷掉。

一個是觀察者的抽象類或者接口,用來更新自己霞势。
觀察者要能做一件事兒烹植,那就是更新自己。

具體舉個例子:
雖然我們主要是寫android的愕贡,但是服務(wù)器的東西還是要懂點草雕,就拿簡書這個類似于博客的網(wǎng)站來說吧,如果你訂閱了或者說關(guān)注了一個領(lǐng)域颂鸿,就能收到這個領(lǐng)域文章的推送促绵,如果沒有關(guān)注,則不能嘴纺。是相當(dāng)于有一個總控制臺(被觀察者败晴,持有數(shù)據(jù)源,這里的數(shù)據(jù)源是我們每個訂閱了的人)通知下面的觀察者栽渴。

在java中可以這么寫尖坤。(因為java的觀察者模式是內(nèi)置的,所以你只需要實現(xiàn)Observer接口和繼承Observable)

觀察者的代碼:

/**
 * 程序員闲擦,也就是你慢味,訂閱這個專題的人是。觀察者
 */

public class Coder implements Observer {
    private String yourName;
    public Coder(String yourName){
        this.yourName=yourName;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("你訂閱的"+arg.toString()+"更新了墅冷。");
    }

    @Override
    public String toString() {
        return "your name "+yourName;
    }
}

以下是被觀察者和服務(wù)器的代碼纯路。

/**
 * 你訂閱的簡書android領(lǐng)域
 */
public class JianShuAndroid extends Observable{
    public void postNewContentToCoder(String content){
        setChanged();
        notifyObservers(content);
    }
}

/**
 * 服務(wù)器的代碼
 */
public class Server{   
    public static void main(String[] args){
        JianShuAndroid jianShuAndroid=new JianShuAndroid();
        Coder coder1=new Coder("name1");
        Coder coder2=new Coder("name2");
        Coder coder3=new Coder("name3");
        jianShuAndroid.addObserver(coder1);
        jianShuAndroid.addObserver(coder2);
        jianShuAndroid.addObserver(coder3);
        jianShuAndroid.postNewContentToCoder("contentChanged");
    }
}

(這里順便提一下,為什么觀察者是實現(xiàn)一個observer接口寞忿,而被觀察者是繼承一個抽象類呢驰唬?我覺得,被觀察者寫成抽象類的原因是復(fù)用腔彰,觀察者寫成接口的原因是降低代碼的耦合度叫编,面向接口編程,在原則里就是依賴倒置原則霹抛,我們倒著思考搓逾,如果這里不是接口,而是一個具體的類杯拐,那么霞篡,耦合度就相當(dāng)高了世蔗,如果不是Coder注冊就無法添加到observable里,就要修改observable的代碼寇损。)

接下來驗證我們看看android的套路是怎么玩的凸郑。
文章開頭提到的RecyclerView,整個列表是一個被觀察者也就是這個RecyclerView的Adapter矛市,為什么要叫被觀察者呢芙沥?因為Adapter持有了所有數(shù)據(jù)源,而每條Item要做的是觀察這些數(shù)據(jù)源有沒有變化浊吏,所以就叫被觀察者而昨,每個Item就叫觀察者。當(dāng)數(shù)據(jù)變化的時候找田,adapter就通知這條Item歌憨,數(shù)據(jù)源發(fā)生了變化了(這個通知的過程是notifyDataSetChanged來進行的),我們可以猜想,RecyclerView的內(nèi)部的模式是觀察者模式墩衙。

玩觀察者模式务嫡,假如你用的是java,那么observable漆改,observer是內(nèi)置的心铃,你的被觀察者繼承observable,觀察者實現(xiàn)observer接口挫剑,就行了去扣。其他的語言,如果你搞清了原理樊破,自己寫一個也很簡單愉棱。

上面我們說到了RecyclerView,開始分析(看源碼哲戚,一萬多行的RecyclerView不要急奔滑,找我們想要的。我感覺看源碼就是像給了你一團毛線讓你理順顺少,當(dāng)然你要去找那個線頭档押,千萬不要陷入泥潭,每一行都看祈纯。)

大家找一下這個突破口,也就是線頭叼耙,我們明顯現(xiàn)在已經(jīng)知道了recyclerview里肯定用到了觀察者模式腕窥,還有一個重要線索是,我們知道它使怎么通知觀察者改變的筛婉,就是notifyDataSetChanged簇爆。那就從這個開始看起癞松。
找了半天,知道了notifyDataSetChanged是在Adapter里面入蛆,而這個adapter開始的第一句就是

public static abstract class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        private boolean mHasStableIds = false;
  ....
  ....
  ....
}

很熟悉的字眼响蓉,observable(被觀察者)來了∩诨伲看一下這個AdapterDataObservable枫甲,不要怕,名字是長了點扼褪,但是一般名字長的都是紙老虎想幻。

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }

        public void notifyChanged() {
            // since onChanged() is implemented by the app, it could do anything, including
            // removing itself from {@link mObservers} - and that could cause problems if
            // an iterator is used on the ArrayList {@link mObservers}.
            // to avoid such problems, just march thru the list in the reverse order.
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }

        public void notifyItemRangeChanged(int positionStart, int itemCount) {
            notifyItemRangeChanged(positionStart, itemCount, null);
        }
  ...
  ...
  ...

一看,沒錯這家伙肯定是被觀察者话浇,因為它繼承了Observable脏毯。我們那個簡書網(wǎng)站就是這樣啊,JianShuAndroid繼承了observable幔崖。
還記得我們前面說被觀察者一般要可以做三件事兒食店。注冊,注銷和通知赏寇。那么這里也不會例外吉嫩。我們看到的是通知這件事兒,但是注冊和注銷是java內(nèi)部的Observable幫我們實現(xiàn)了蹋订。

public abstract class Observable<T> {
    /**
     * The list of observers.  An observer can be in the list at most
     * once and will never be null.
     */
    protected final ArrayList<T> mObservers = new ArrayList<T>();

    /**
     * Adds an observer to the list. The observer cannot be null and it must not already
     * be registered.
     * @param observer the observer to register
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is already registered
     */
    public void registerObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            if (mObservers.contains(observer)) {
                throw new IllegalStateException("Observer " + observer + " is already registered.");
            }
            mObservers.add(observer);
        }
    }

    /**
     * Removes a previously registered observer. The observer must not be null and it
     * must already have been registered.
     * @param observer the observer to unregister
     * @throws IllegalArgumentException the observer is null
     * @throws IllegalStateException the observer is not yet registered
     */
    public void unregisterObserver(T observer) {
        if (observer == null) {
            throw new IllegalArgumentException("The observer is null.");
        }
        synchronized(mObservers) {
            int index = mObservers.indexOf(observer);
            if (index == -1) {
                throw new IllegalStateException("Observer " + observer + " was not registered.");
            }
            mObservers.remove(index);
        }
    }
    ....
    ....
    ....

這時候observable出現(xiàn)了率挣,那observer呢?
其實上面就已經(jīng)出現(xiàn)了露戒〗饭Γ回上一段代碼里找,AdapterDataObserver的泛型里是AdapterDataObserver智什,然后我們?nèi)炙岩幌戮涂梢园l(fā)現(xiàn)

private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            if (mAdapter.hasStableIds()) {
                // TODO Determine what actually changed.
                // This is more important to implement now since this callback will disable all
                // animations because we cannot rely on positions.
                mState.mStructureChanged = true;
                setDataSetChangedAfterLayout();
            } else {
                mState.mStructureChanged = true;
                setDataSetChangedAfterLayout();
            }
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }
      ...
      ...
      ...

這就是observer了动漾,這個是干嘛的,根據(jù)我們上面說的荠锭,有一件事兒旱眯,那就是更新數(shù)據(jù)的,onChanged里面涉及到requestLayout()的证九,在requestLayout里會重新調(diào)用onDraw來重繪删豺。

最后整理一遍這個過程,
在Adapter里面有一個AdapterDataObservable愧怜,是被觀察者呀页,被觀察者必須有三個方法,注冊拥坛,銷毀蓬蝶,通知尘分,這里的注冊就是registerAdapterDataObserver,通知就是notify相關(guān)的丸氛。
在setAdapter的時候培愁,將觀察者,也就是RecyclerViewDataObserver注冊到AdapterDataObservable里面來維護缓窜,觀察者里面自然是更新布局定续。
我們調(diào)用notifyDataSetChanged其實就是調(diào)用被觀察者的notify相關(guān)方法

關(guān)于觀察者模式,有興趣的話可以看看EventBus和AndroidEventBus的源碼雹洗,這兩個開源庫的實現(xiàn)也是運用了觀察者模式香罐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市时肿,隨后出現(xiàn)的幾起案子庇茫,更是在濱河造成了極大的恐慌,老刑警劉巖螃成,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旦签,死亡現(xiàn)場離奇詭異,居然都是意外死亡寸宏,警方通過查閱死者的電腦和手機宁炫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氮凝,“玉大人羔巢,你說我怎么就攤上這事≌终螅” “怎么了竿秆?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長稿壁。 經(jīng)常有香客問我幽钢,道長,這世上最難降的妖魔是什么傅是? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任匪燕,我火速辦了婚禮,結(jié)果婚禮上喧笔,老公的妹妹穿的比我還像新娘帽驯。我一直安慰自己,他們只是感情好书闸,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布尼变。 她就那樣靜靜地躺著,像睡著了一般梗劫。 火紅的嫁衣襯著肌膚如雪享甸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天梳侨,我揣著相機與錄音蛉威,去河邊找鬼。 笑死走哺,一個胖子當(dāng)著我的面吹牛蚯嫌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丙躏,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼择示,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了晒旅?” 一聲冷哼從身側(cè)響起栅盲,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎废恋,沒想到半個月后谈秫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡鱼鼓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年拟烫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迄本。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡硕淑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嘉赎,到底是詐尸還是另有隱情置媳,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布曹阔,位于F島的核電站半开,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赃份。R本人自食惡果不足惜寂拆,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抓韩。 院中可真熱鬧纠永,春花似錦、人聲如沸谒拴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽英上。三九已至炭序,卻和暖如春啤覆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惭聂。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工窗声, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辜纲。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓笨觅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耕腾。 傳聞我的和親對象是個殘疾皇子见剩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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