1. 程式人生 > >Android設計模式(十一)-觀察者模式

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中的訊息預設是順序執行,如果一個觀察者卡頓,會造成整個系統效率變低,可以考慮非同步。
  • 可能會引起無用的操作甚至錯誤的操作。