Android設計模式(十一)-觀察者模式
觀察者模式是一種使用頻率非常高的設計模式,最常用的地方就是訂閱-釋出系統。
這個模式的重要作用就是將觀察者和被觀察者解耦,使他們之間的依賴更小甚至沒有。
定義
定義物件一種一對多的依賴關係,使得每當一個物件改變狀態,則所有依賴於他的物件都會得到通知並被自動更新。
使用場景
- 關聯行為場景,這個關聯是可拆分的。將觀察者和被觀察者封裝在不同的物件中,可以各自獨立的變化。
- 當一個物件改變時,有其他物件要進行相應的變化,但是他並不知道有多少個物件需要變化。
- 跨系統的訊息交換長江,如訊息佇列,時事件匯流排等
UML
- Subject : 抽象被觀察者(Observeable),吧所有觀察者物件的醫用儲存在一個集合裡,每個主題都可以有任意數量的觀察者,抽象被觀察者提供一個介面,可以增加和刪除觀察者物件。
- ConcreteSubject: 具體的被觀察者,將有關狀態存入具體的觀察者物件,在具體的被觀察者內部狀態發生變化時,給所有註冊的觀察者傳送通知。
- Observer : 抽象觀察者,定義了一個更新介面,使得在得到被觀察者的通知時更新自己。
- ConcreteObserver : 具體的觀察者,實現了抽象觀察者鎖定義的介面,用來在收到通知時更新自己。
簡單實現
訂閱模式就是個觀察者模式,訂閱後,被訂閱的有更新就會提示你。
拿微信公眾號舉個例子吧.Java提供的有Observer和Observable類,可以很方便的實現觀察者模式。
先定義一個訂閱者,實現更新方法。
public class User implements Observer {
public String name;
public User(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("Hi "+name +",公眾號更新了內容:"+arg);
}
}
定義一個可觀察者,有變化時釋出更新通知。
public class Gamedaily extends Observable {
public void postNewArticle(String content){
//內容發生改變
setChanged();
//通知所有訂閱者改變的內容
notifyObservers(content);
}
}
使用
public class Client {
public static void main(String[] args) {
Gamedaily gamedaily = new Gamedaily();
User user1 = new User("user1");
User user2 = new User("user2");
User user3 = new User("user3");
//將觀察者註冊到可觀察者的通知列表中。
gamedaily.addObserver(user1);
gamedaily.addObserver(user2);
gamedaily.addObserver(user3);
gamedaily.postNewArticle("新文章來了");
}
}
輸出
當公眾號釋出新文章的時候,所有訂閱者都收到的通知,並作出相應的改變。一個公眾號對應多個訂閱者,並且完全沒有耦合。
Android原始碼中的觀察者模式
通常在ListView的內容變化時,我們會呼叫notifyDataSetChanged()
這個方法,然後ListView裡面的資料就會進行更新。這個感覺就像是觀察者模式。ListView在觀察者內容,內容變化釋出通知之後ListView就會更新資料。
看一下這個方法。
package android.widget;
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();
}
......
}
這段程式碼可以看到這應該是一個觀察者模式,而且這個一個被觀察者,裡面提供了註冊和登出觀察者以及通知觀察者的方法。
這些方法是通過DataSetObservable這個類呼叫的:
package android.database;
public class DataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
......
}
這個類繼承自Observable,Observable中有一個protected final ArrayList<T> mObservers = new ArrayList<T>();
,
用來儲存註冊的觀察者。mDataSetObservable.registerObserver(observer)
和mDataSetObservable.unregisterObserver(observer)
分別就是增加和刪除。
在notifyChanged
方法中,迴圈這個集合,呼叫每一個觀察者的onChanged()
方法。
那麼這些觀察者是什麼時候註冊的呢?也就是ListView和Adapter什麼時候成了訂閱關係。在ListView的setAdapter()
中
public class ListView extends AbsListView {
public void setAdapter(ListAdapter adapter) {
//如果已經有了一個adapter,登出這個adapter之前的觀察者,
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
......
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
//將新的adapter賦給mAdapter
mAdapter = adapter;
}
......
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
//儲存之前的資料個數
mOldItemCount = mItemCount;
//獲取新的個數
mItemCount = mAdapter.getCount();
checkFocus();
//建立資料集觀察者
mDataSetObserver = new AdapterDataSetObserver();
//註冊觀察者
mAdapter.registerDataSetObserver(mDataSetObserver);
...
}
} else {
...
}
requestLayout();
}
}
AdapterDataSetObserver是ListView的父類AbsListView的內部類
package android.widget;
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback {
class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
@Override
public void onChanged() {
super.onChanged();
if (mFastScroll != null) {
mFastScroll.onSectionsChanged();
}
}
······
}
}
AdapterDataSetObserver是AdapterView.AdapterDataSetObserver的子類,所以要看super.onChanged()
package android.widget;
public abstract class AdapterView<T extends Adapter> extends ViewGroup {
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();
}
......
}
}
整理一下:當ListView資料變化時,呼叫Adapter的notifyDataSetChange方法,這個方法呼叫DataSetObservable的notifyChanged方法,這個方法又會呼叫所有觀察者的onChanged方法,onChanged再呼叫重新佈局View的方法,完成重新整理資料的功能。
總結
優點
- 解除了觀察者和被觀察者的耦合,而且依賴的都是抽象,容易應對業務變化,各自的變化都不會影響另一個。
- 增強系統靈活性、可拓展性。
缺點
- Java中的訊息預設是順序執行,如果一個觀察者卡頓,會造成整個系統效率變低,可以考慮非同步。
- 可能會引起無用的操作甚至錯誤的操作。