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)也是運用了觀察者模式香罐。