1 定義
定義對象之間的一種一對多依賴關系,使得每當一個對象狀態(tài)發(fā)生改變時皱炉,其相關依賴對象皆得到通知并被自動更新怀估。觀察者模式的別名包括發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式合搅、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式多搀。觀察者模式是一種對象行為型模式。
要點就是灾部,觀察者模式定義了一些列對象之間的一對多的關系康铭,當一個對象改變狀態(tài),其他依賴者都會受到通知梳猪。
知曉定義之后玉吁,我們仍然需要了解他的應用場景:一個軟件系統(tǒng)常常要求在某一個對象的狀態(tài)發(fā)生變化的時候痹愚,某些其他的對象做出相應的改變。做到這一點的設計方案有很多贾惦,但是為了使系統(tǒng)能夠易于復用叠荠,應該選擇低耦合度的設計方案匿沛。減少對象之間的耦合有利于系統(tǒng)的復用,但是同時設計師需要使這些低耦合度的對象之間能夠維持行動的協(xié)調一致榛鼎,保證高度的協(xié)作逃呼。觀察者模式是滿足這一要求的各種設計方案中最重要的一種(此段摘抄,因為總結的真的好= =)者娱。
2 類圖
- Subject:主題接口(被觀察者)抡笼,每個主題可以有多個觀察者。抽象主題提供了添加黄鳍,刪除和通知觀察者的抽象方法推姻。
- Observer: 觀察者接口,所有潛在的觀察者必須實現它框沟,這個接口只有一個update方法藏古, 當主題改變時被調用增炭。
- ConcreteSubject:具體的主題,實現了抽象方法拧晕,在notifyObservser隙姿,通知所有已添加的觀察者。
- ConcreteObserver:具體的觀察者厂捞,可以是任意類输玷,必須實現update方法,當需要監(jiān)測主題的狀態(tài)變化靡馁,通過主題的add方法訂閱饲嗽。
在任何時候我們都可以增加新的觀察者,主題唯一依賴的是一個實現Observer接口的對象列表奈嘿,當有新的類出現時貌虾,我們無需修改主題的代碼,只需要將新類型實現觀察者接口裙犹,然后注冊為觀察者即可尽狠。可以看到叶圃,修改具體主題或者觀察者其中一方都不會影響另外一方袄膏,這種松耦合的設計更能應對變化,將對象之間的互相依賴降到最低掺冠,建立有彈性的OO系統(tǒng)沉馆。
3 簡單實現
正好最近室友在玩彩票,我們就用彩票例子來做一個簡單的實現德崭。假定彩票莊家是被觀察者斥黑,那么我們這些買彩票的玩家則為觀察者,我們通過買彩票跟莊家綁定訂閱關系眉厨,開獎的時候锌奴,莊家通知所有玩家中獎號碼,我們則會知道自己有沒有中獎憾股。
//抽象觀察者
interface UserObserver {
void update(String winCode);
}
//具體觀察者(彩票玩家)
class LotteryTicketUser implements UserObserver {
private String code;
private String name;
public LotteryTicketUser(String code, String name) {
this.code = code;
this.name = name;
}
@Override
public void update(String winCode) {
System.out.println("我是" + name + (winCode.equals(code) ? "我中獎了鹿蜀!" : "我沒中獎"));
}
}
//抽象被觀察者(莊家)
interface Banker {
//提供添加,刪除服球,和通知觀察者的方法
void add(UserObserver observer) ;
void remove(UserObserver observer) ;
void notify(String winCode);
}
//具體被觀察者(黑心莊家)
class BlackBanker implements Banker {
private List<UserObserver> list = new ArrayList<>();
@Override
public void add(UserObserver observer) {
list.add(observer);
}
@Override
public void remove(UserObserver observer) {
list.remove(observer);
}
@Override
public void notify(String winCode) {
for (UserObserver observer : list) {
observer.update(winCode);
}
}
}
//客戶端
public class Main {
public static void main(String[] args) {
BlackBanker blackBanker = new BlackBanker();
LotteryTicketUser user1 = new LotteryTicketUser("111","黑子");
LotteryTicketUser user2 = new LotteryTicketUser("222","軍哥");
LotteryTicketUser user3 = new LotteryTicketUser("333","大將");
blackBanker.add(user1);
blackBanker.add(user2);
blackBanker.add(user3);
blackBanker.notify("444");
}
}
//打印
我是黑子我沒中獎
我是軍哥我沒中獎
我是大將我沒中獎
中獎茴恰?不存在的。
4 兩種實現方式
簡單實現的只是一種實現方式斩熊,其實根據側重的功能往枣,觀察者分為兩種實現方式:
1.推模型
顧名思義,即被觀察者通知觀察者時,無論觀察者是否需要婉商,都會將全部或者部分信息推送至觀察者似忧,這些信息一般通過通知觀察者的方法(例如上面的update)傳遞。
2.拉模型
觀察者在接收通知的時候丈秩,可能不需要太多信息盯捌,而是在需要的時候自己去拉取,于是拉模型應運而生蘑秽。這種實現方式饺著,被觀察者只向觀察推送少量信息,一般來說是將自己的引用傳遞過去肠牲,以便觀察者在需要的時候幼衰,獲取自己的全部信息。
這里可以看到我們的簡單實現只是一種推的方式缀雳,如果是拉模型的場景渡嚣,可能就是比如有玩家需要知道莊家各種中獎信息,足彩肥印,籃彩各種等等识椰,那么這里我們可以通過update方法將莊家自身引用傳遞給玩家,玩家通過莊家提供一個獲取所有中獎的方法來查看深碱,關于具體代碼就不做演示了腹鹉。
5 案例分析
觀察者模式可謂是應用極廣,例如火了有一段時間的RxJava敷硅,EventBus這些消息隊列功咒、事件總線,又比如android中天天寫的OnclickListener(一對一)绞蹦,Adapter的notify力奋,甚至是MVC,都跟觀察者模式息息相關坦辟。太多的實例大家有興趣都可以研究一下刊侯,接下來我選其中一個結合源碼來探討一番。
1.Adapter的notifyDataSetChanged()
日常的開發(fā)離不開setAdapter锉走,同時使用Adapter的notifyDataSetChanged方法通知ListView數據刷新,先看BaseAdapter源碼:
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中包含了一個觀察者集合mDataSetObservable藕届,同時提供了增加挪蹭,刪除,通知訂閱者的方法休偶,我們來看mDataSetObservable.notifyChanged()
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
}
也就是遍歷觀察者們并調用他們的onChange方法梁厉。我們知道ListView和Adapter是通過setAdapter產生訂閱關系的,接下來來看這個方法:
@Override
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
...
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
//創(chuàng)建觀察者并為adapter添加觀察者
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
...
}
很明顯在setAdapter中,新建了觀察者并綁定了訂閱關系词顾。從前面的分析得知八秃,notify的時候會調用觀察者的onChange方法,我們來看看觀察者的onChange:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroller != null) {
mFastScroller.onSectionsChanged();
}
}
}
啥也沒有肉盹,我們點到supet.onChange()里面:
class AdapterDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
//注意這里
requestLayout();
}
}
一大串昔驱,但是我們只需要關注這里的requestLayout(),他通知了ListView的重新布局上忍。
小總結:
Adapter為被觀察者骤肛,ListView為觀察者(準確來說是ListView包含了一個觀察者),兩者通過setAdapter綁定發(fā)布-訂閱的關系(Adapter添加了這個觀察者)窍蓝。當Adapter調用notifyDataSetChanged()時腋颠,他就會遍歷他所添加的觀察者們(此處即為ListView中的觀察者),并調用他們的onChange()吓笙。而在onChange()中淑玫,會調用requestLayout(),從而使得ListView重新布局面睛,達到數據刷新的目的絮蒿。
要點總結
- 觀察者模式定義了對象之間一對多的關系(事件監(jiān)聽等屬于特殊的一對一的觀察者模式)。
- 松耦合設計侮穿,被觀察者不知道觀察者的實現細節(jié)歌径,只知道觀察者實現了自己通知他們的方法。
- 觀察者模式的兩種實現方式亲茅,推模型和拉模型回铛。
- 有多個觀察者時,不可以依賴特定的通知次序克锣。
- 當一個主題有很多觀察者時茵肃,那么通知所有觀察者所花費的時間也是很多的。
- 如果在被觀察者之間有循環(huán)依賴的話袭祟,被觀察者會觸發(fā)它們之間進行循環(huán)調用验残,導致系統(tǒng)崩潰,使用觀察者模式需要尤其注意巾乳。