十一、觀察者設計模式
1. 觀察者模式的介紹
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。
例如:我們在使用應用市場下載應用時,我們的通知欄會有下載進度顯示,我們的詳情頁會有進度顯示,我們的列表中也會有下載進度顯示,這就是一個典型的觀察者設計模式,多個觀察者監聽同一個下載進度。
2. 觀察者模式的使用場景
- 事件的多級觸發場景。
- 跨系統的訊息交換場景。
3. 觀察者模式的UML類圖
UML角色介紹
Subject: 抽象主題角色,也就是被觀察的角色,抽象主題角色的所有觀察者物件的引用儲存在一個集合裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件。
ConcreteSuject: 將有關狀態存入具體觀察者物件,在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。
Obser: 為所有具體觀察者定義一個介面,在得到主題的通知時更新自己。這個介面叫做更新介面。
ConcreteObserver: 具體的觀察者,該角色實現抽象觀察者角色所定義的更新介面,以便在主題的狀態發生變化時更新自身的狀態。
4. 觀察者模式的簡單實現
(1)、 首先定義一個觀察者介面:
public interface Observer {
/**
* 更新介面
*
* @param subject 傳入主題物件,獲取主題的狀態資訊
*/
public void update(Subject subject);
}
觀察者接口裡面只有一個方法update(Subject suject),當有更新時,將被觀察者物件傳進來,然後獲取其狀態資訊。
(2)、接著定義具體的觀察者,實現觀察者介面:
public class ConcreteObserver implements Observer {
private ConcreteSubject concreteSubject;
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(Subject subject) {
concreteSubject = (ConcreteSubject) subject;
String state = concreteSubject.getState();
System.out.println(name + " 觀察到: " + state);
}
}
具體觀察者在收到更新後,在update方法裡面做出具體的操作
(3)、然後,定義具體的被觀察物件
public abstract class Subject {
//用來儲存註冊的觀察者
private List<Observer> observers = new ArrayList<Observer>();
/**
* 註冊觀察者,將其加入到集合中
*
* @param observer
*/
public void registerObserver(Observer observer) {
if (!observers.contains(observer)) {
observers.add(observer);
}
}
/**
* 取消註冊
*
* @param observer
*/
public void unrigisterObserver(Observer observer) {
if (observers.contains(observer)) {
observers.remove(observer);
}
}
/**
* 通知所有的觀察者
*/
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this);
}
}
}
被觀察者裡有一個集合,使用者儲存所有的觀察者物件的引用。
使用registerObserver方法新增到集合中,使用unregisterObserver方法將觀察者物件從集合中移除。
最後呼叫notifyObservers方法遍歷所有的觀察者,呼叫它們的update方法,將被觀察者自身物件傳遞進去。
具體的每個觀察者在自己的update方法裡面做出相應的行為。
(4)、具體的被觀察者
public class ConcreteSubject extends Subject {
private String state;
public String getState() {
return state;
}
public void change(String newSate) {
this.state = newSate;
this.notifyObservers();
}
}
這個具體的觀察者,其實在日常開發中,根本沒有抽象觀察者,都只有一個具體的被觀察者,這裡為了實現UML類圖裡面的對應關係,所以多了一個抽象的觀察者。
5. 觀察者模式在Android原始碼中
我們平時在更新ListView時,都會使用notifyDataSetChanged()方法來更新介面,其實這就是一個觀察者設計模式。
我們先簡單來說一下大致步驟。
(1)、首先我們會通過setAdapter來設定Adapter,在Adapter裡面設定了一個觀察者。觀察者裡面有一個changed()方法,onChanged()方法面會重新重新整理佈局。
(2)、當我們的資料發生變化時,會呼叫Adapter裡面的notifyDataSetChanged()方法來更新資料,在notifyDataSetChanged()方法裡面,最終會遍歷所有的的觀察者,並呼叫其changed()方法。
下面我們從原始碼的角色:
(1)、首先我們我們先找出觀察者,從剛剛的分析中,我們知道setAdapter會設定一個觀察者。所以我們從setAdapter入手。
public void setAdapter(ListAdapter adapter) {
//忽略以上程式碼
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
//這段程式碼就是觀察者。
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
從以上程式碼我們可以看到,setAdapter裡面我們看到如下程式碼:
mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver);
從名字我們不難猜測到這是一個觀察者。我們繼續點進去,在AbsListView中我們看到如下程式碼:
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
@Override
public void onInvalidated() {
super.onInvalidated();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
}
我們發現AdapterDataSetObserver繼承自AdapterView.AdapterDataSetObserver,繼續跟進:
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();
requestLayout();
}
@Override
public void onInvalidated() {
mDataChanged = true;
if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
}
// Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false;
checkFocus();
requestLayout();
}
上述程式碼就是我們想要的觀察者的廬山真面目。裡面我們注意發現有onChanged()方法,這個就相當於UML類圖的update()方法。
(2)、 接著我們找出被觀察物件。
我們從BaseAdapter裡面的notifyDataSetChanged()方法入手,我們看到如下程式碼:
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
同時我們可以看到如下程式碼:
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
從方法的名字可以看出,以上兩個方法的作用是,註冊觀察者和反註冊觀察者。
我們繼續跟進,看到如下程式碼:
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();
}
}
}
}
從上面的程式碼中我們可以看到,notifyChanged()方法遍歷了所有的觀察者,並呼叫了其onChanged()方法。
分析到這裡,我們可以肯定的說,這就是一個典型的觀察者設計模式。
最後畫張圖來總結一下,讓呼叫更加清晰明瞭。
6. 觀察者模式的Android開發
下面模擬我們在應用市場下載通過一個應用時,可以同時在通知欄,詳情頁面,ListView同時觀察到同步的進度。模擬每隔一秒鐘將下載進度發給所有的觀察者。
(1)、首先定義一個觀察者介面介面:Listener
public interface Listener {
//更新進度的方法
void update(int progress);
}
(2)、具體的觀察者:DownLoadListener
public class DownListener implements Listener {
//觀察者的名稱
public String name;
public DownListener(String name) {
this.name = name;
}
//具體觀察者在收到更新後所進行的操作
@Override
public void update(int progress) {
System.out.println(name + " ,下載進度: " + progress);
}
}
(3)、下載服務DownloadService。模擬下載服務主要用到了計時器工具類,Timer和TimerTask,每隔一秒鐘,通過handler將進度傳送出去。handler收到進度後,遍歷所有的觀察者,呼叫所有監聽的觀察者的update方法,將進度傳遞進去。
public class DownloadService {
private List<Listener> listeners = new ArrayList<Listener>();
private static final int DOWN = 1;
private int progress = 1;
private Timer mTimer;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case DOWN:
//通知所有的觀察者
notifyAllListeners(msg.arg1);
break;
default:
break;
}
}
};
public DownloadService() {
mTimer = new Timer();
}
//模擬開啟服務
public void startService() {
setTimerTask();
}
//定時服務
private void setTimerTask() {
mTimer.schedule(new TimerTask() {
@Override
public void run() {
Message message = new Message();
message.arg1 = ++progress;
message.what = DOWN;
handler.sendMessage(message);
}
}, 1000, 1000/* 表示1000毫秒之後,每隔1000毫秒執行一次 */);
}
//遍歷通知所有的觀察者
public void notifyAllListeners(int progress) {
for (Listener listener :
listeners) {
listener.update(progress);
}
}
//註冊監聽
public void registerService(Listener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
//取消監聽
public void ungisterService(Listener listener) {
if (listeners.contains(listener)) {
listeners.remove(listener);
}
}
}
(4)、 測試程式碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//構造三個觀察者
Listener listener1 = new DownListener("觀察者1");
Listener listener2 = new DownListener("觀察者2");
Listener listener3 = new DownListener("觀察者3");
//下載服務
DownloadService downloadService = new DownloadService();
//註冊監聽者
downloadService.registerService(listener1);
downloadService.registerService(listener2);
downloadService.registerService(listener3);
//開啟服務
downloadService.startService();
}
}
7. 總結
- 優點:
- 觀察者與被觀察者之間屬於輕度的關聯關係,兩個之間是抽象呢耦合的。易於擴充套件。
- 缺點:
- 由於觀察者模式是一條觸發鏈,當觀察者比較多的時候,靠能會導致執行效率下降,一個觀察者的卡頓,會導致整整體的執行效率。