【設計模式】觀察者模式

引子

  還記得警匪片上,匪徒們是怎麼配合實施犯罪的嗎?設計模式

  一個團伙在進行盜竊的時候,總有一兩我的在門口把風——若是有什麼風吹草動,則會當即通知裏面的同夥緊急撤退。ide

  也許放風的人並不必定認識裏面的每個同夥;測試

  而在裏面也許有新來的小弟不認識這個放風的。ui

  可是這沒什麼,這個影響不了他們之間的通信,由於他們之間有早已商定好的暗號。spa

  呵呵,上面提到的放風者、偷竊者之間的關係就是觀察者模式在現實中的活生生的例子。設計

定義與結構

  觀察者(Observer)模式又名發佈-訂閱(Publish/Subscribe)模式。3d

  GOF 給觀察者模式以下定義:code

  定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。server

  在這裏先提一下面向對象設計的一個重要原則:單一職責原則對象

  系統的每一個對象應該將重點放在問題域中的離散抽象上。

  所以理想的狀況下,一個對象只作一件事情

  這樣在開發中也就帶來了諸多的好處:提供了重用性和維護性,也是進行重構的良好的基礎。

  所以幾乎全部的設計模式都是基於這個基本的設計原則來的。

  觀察者模式的起源應該是在 GUI 和業務數據的處理上,由於如今絕大多數講解觀察者模式的例子都是這一題材。

  可是觀察者模式的應用決不只限於此一方面。

觀察者模式的組成部分

  1. 抽象目標角色(Subject):目標角色知道它的觀察者,能夠有任意多個觀察者觀察同一個目標。而且提供註冊和刪除觀察者對象的接口。目標角色每每由抽象類或者接口來實現。
  2. 抽象觀察者角色(Observer):爲那些在目標發生改變時須要得到通知的對象定義一個更新接口。抽象觀察者角色主要由抽象類或者接口來實現。
  3. 具體目標角色(Concrete Subject):將有關狀態存入各個 Concrete Observer 對象。當它的狀態發生改變時, 向它的各個觀察者發出通知。
  4. 具體觀察者角色(Concrete Observer):存儲有關狀態,這些狀態應與目標的狀態保持一致。實現 Observer 的更新接口以使自身狀態與目標的狀態保持一致。在本角色內也能夠維護一個指向 Concrete Subject 對象的引用。

觀察者模式的類圖

   

 

  在 Subject 這個抽象類中,提供了上面提到的功能,並且存在一個通知方法:notify。

  還能夠看到 Subject 和 ConcreteSubject 之間能夠說是使用了模板模式(這個模式真是簡單廣泛到一不當心就用到了)。

  這樣當具體目標角色的狀態發生改變,按照約定則會去調用通知方法,在這個方法中則會根據目標角色中註冊的觀察者名單來逐個調用相應的 update 方法來調整觀察者的狀態。

  這樣觀察者模式就走完了一個流程。

舉例

   怎麼才能將業務邏輯和顯示結果的界面很好的分離開?不用問,就是觀察者模式!

   看看 JUnit 中觀察者模式的使用代碼:

   下面是抽象觀察者角色,JUnit 是採用接口來實現的,這也是通常採用的方式

    //下面是咱們的抽象觀察者角色,JUnit 是採用接口來實現的,這也是通常採用的方式。
    //能夠看到這裏面定義了四個不一樣的 update 方法,對應四種不一樣的狀態變化
    public interface TestListener {
        /**
         * An error occurred.
         */
        public void addError(Test test, Throwable t);

        /**
         * A failure occurred.
         */
        public void addFailure(Test test, AssertionFailedError t);

        /**
         * A test ended.
         */
        public void endTest(Test test);

        /**
         * A test started.
         */
        public void startTest(Test test);
    }
View Code

 

   具體觀察者角色,這裏採用最簡單的 TextUI 下的狀況來講明

    //具體觀察者角色,咱們採用最簡單的 TextUI 下的狀況來講明
    public class ResultPrinter implements TestListener {
        //省略好多啊,主要是顯示代碼
        //    ……
        //下面就是實現接口 TestListener 的四個方法
        //填充方法的行爲很簡單的說
        /**
         * @see junit.framework.TestListener#addError(Test, Throwable)
         */
        public void addError(Test test, Throwable t) {
            getWriter().print("E");
        }

        /**
         * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
         */
        public void addFailure(Test test, AssertionFailedError t) {
            getWriter().print("F");
        }

        /**
         * @see junit.framework.TestListener#endTest(Test)
         */
        public void endTest(Test test) {
        }

        /**
         * @see junit.framework.TestListener#startTest(Test)
         */
        public void startTest(Test test) {
            getWriter().print(".");
            if (fColumn++ >= 40) {
                getWriter().println();
                fColumn = 0;
            }
        }
    }
View Code

 

  看下目標角色,因爲JUnit 功能的簡單,只有一個目標——TestResult,所以 JUnit 只有一個具體目標角色。

//好長的代碼,好像沒有重點。去掉了大部分與主題無關的信息
//下面只列出了當 Failures 發生時是怎麼來通知觀察者的
public class TestResult extends Object {
    //這個是用來存放測試 Failures 的集合
    protected Vector fFailures;
    //這個就是用來存放註冊進來的觀察者的集合
    protected Vector fListeners;

    public TestResult() {
        fFailures = new Vector();
        fListeners = new Vector();
    }

    /**
     * Adds a failure to the list of failures. The passed in exception
     * caused the failure.
     */
    public synchronized void addFailure(Test test, AssertionFailedError t) {
        fFailures.addElement(new TestFailure(test, t));
        //下面就是通知各個觀察者的 addFailure 方法
        for (Enumeration e = cloneListeners().elements(); e.hasMoreElements();) {
            ((TestListener) e.nextElement()).addFailure(test, t);
        }
    }

    /**
     * 註冊一個觀察者
     */
    public synchronized void addListener(TestListener listener) {
        fListeners.addElement(listener);
    }

    /**
     * 刪除一個觀察者
     */
    public synchronized void removeListener(TestListener listener) {
        fListeners.removeElement(listener);
    }

    /**
     * 返回一個觀察者集合的拷貝,固然是爲了防止對觀察者集合的非法方式操做了
     * 能夠看到全部使用觀察者集合的地方都經過它
     */
    private synchronized Vector cloneListeners() {
        return (Vector) fListeners.clone();
    }

}
View Code

 

  觀察者模式組成所須要的角色在這裏已經全了。不過好像仍是缺點什麼……。呵呵

  對!就是它們之間尚未真正的創建聯繫。在 JUnit 中是經過 TestRunner 來做的,而你在具體的系統中能夠靈活掌握。

  看一下 TestRunner 中的代碼:

    public class TestRunner extends BaseTestRunner {
        private ResultPrinter fPrinter;

        public TestResult doRun(Test suite, boolean wait) {
            //就是在這裏註冊的
            result.addListener(fPrinter);
            …………
        }
    }
View Code

 

使用狀況

  GOF 給出瞭如下使用觀察者模式的狀況:

  1. 當一個抽象模型有兩個方面, 其中一個方面依賴於另外一方面。將這兩者封裝在獨立的對象中以使它們能夠各自獨立地改變和複用。
  2. 當對一個對象的改變須要同時改變其它對象, 而不知道具體有多少對象有待改變。
  3. 當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不但願這些對象是緊密耦合的。

我推你拉

  觀察者模式在關於目標角色、觀察者角色通訊的具體實現中,有兩個版本。

  1. 一種狀況即是目標角色在發生變化後,僅僅告訴觀察者角色「我變化了」;觀察者角色若是想要知道具體的變化細節,則就要本身從目標角色的接口中獲得。這種模式被很形象的稱爲:拉模式——就是說變化的信息是觀察者角色主動從目標角色中「拉」出來的。
  2. 還有一種方法,那就是目標角色「服務一條龍」,通知你發生變化的同時,經過一個參數將變化的細節傳遞到觀察者角色中去。這就是:推模式——無論你要不要,先給你啦。

  這兩種模式的使用,取決於系統設計時的須要。

  1. 若是目標角色比較複雜,而且觀察者角色進行更新時必須獲得一些具體變化的信息,則「推模式」比較合適。
  2. 若是目標角色比較簡單,則「拉模式」就很合適啦。

 

  @成鵬致遠

(blogs:lcw.cnblogs.com

(emailwwwlllll@126.com)

(qq552158509)

相關文章
相關標籤/搜索