1. 程式人生 > >十一、觀察者設計模式

十一、觀察者設計模式

1. 觀察者模式的介紹

觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。

例如:我們在使用應用市場下載應用時,我們的通知欄會有下載進度顯示,我們的詳情頁會有進度顯示,我們的列表中也會有下載進度顯示,這就是一個典型的觀察者設計模式,多個觀察者監聽同一個下載進度。

2. 觀察者模式的使用場景

  • 事件的多級觸發場景。
  • 跨系統的訊息交換場景。

3. 觀察者模式的UML類圖

觀察者設計模式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()方法。

分析到這裡,我們可以肯定的說,這就是一個典型的觀察者設計模式。

最後畫張圖來總結一下,讓呼叫更加清晰明瞭。

Adapter中的觀察者設計模式

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. 總結

  • 優點:
    • 觀察者與被觀察者之間屬於輕度的關聯關係,兩個之間是抽象呢耦合的。易於擴充套件。
  • 缺點:
    • 由於觀察者模式是一條觸發鏈,當觀察者比較多的時候,靠能會導致執行效率下降,一個觀察者的卡頓,會導致整整體的執行效率。