一塊兒學設計模式 - 觀察者模式

觀察者模式(Observer Pattern)屬於對象行爲型模式的一種,定義對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。html

<!-- more -->java

概述

觀察者模式是一種使用率極高的模式,用於創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應做出反應。在觀察者模式中,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間能夠沒有任何相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展。git

觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式設計模式

案例

前言:觀察者模式有兩種方模型,分別是推模型拉模型數組

  • 推模型: 主題對象向觀察者推送主題的詳細信息,無論觀察者是否須要,推送的信息一般是主題對象的所有或部分數據。該模式下若是推送數據變了觀察者都得改
  • 拉模型: 主題對象在通知觀察者的時候,只傳遞少許信息。若是觀察者須要更具體的信息,由觀察者主動到主題對象中獲取,至關因而觀察者從主題對象中拉數據。通常這種 模型的實現中,會把主題對象自身經過update()方法傳遞給觀察者,這樣在觀察者須要獲取數據的時候,就能夠經過這個引用來獲取了。

UML結構圖安全

觀察者模式UML結構圖

  • 抽象主題(Subject)角色: 將觀察者對象的引用保存在一個彙集(好比ArrayList對象)裏,每一個主題均可以有任何數量的觀察者。抽象主題提供接口,能夠增長和刪除觀察者對象。抽象主題角色又叫作抽象被觀察者(Observable)角色。
  • 具體主題(ConcreteSubject)角色: 將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給全部登記過的觀察者發出通知。具體主題角色又叫作具體被觀察者(Concrete Observable)角色。
  • 抽象觀察者(Observer)角色: 爲全部的具體觀察者定義一個更新接口,在獲得主題的通知時更新本身。
  • 具體觀察者(ConcreteObserver)角色: 觀察者的具體實現對象,實現抽象觀察者角色所要求的更新接口,以便使自己的狀態與主題的狀態相協調。若是須要,具體觀察者角色能夠保持一個指向具體主題對象的引用。

推模式

1.定義目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口微信

class Subject {
    /**
     * 用來保存註冊的觀察者對象
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 註冊觀察者對象
     *
     * @param observer 觀察者對象
     */
    void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 通知全部註冊的觀察者對象
     */
    void notifyObservers(String newState) {
        for (Observer observer : observers) {
            observer.update(newState);
        }
    }
}

2.具體的目標對象,負責把有關狀態存入到相應的觀察者對象,並在本身狀態發生改變時,通知各個觀察者多線程

class ConcreteSubject extends Subject {

    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void change(String subjectState) {
        this.subjectState = subjectState;
        //狀態發生改變,通知各個觀察者
        this.notifyObservers(subjectState);
    }
}

3.建立觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象架構

interface Observer {
    /**
     * 更新的接口
     *
     * @param subject 傳入目標對象,好獲取相應的目標對象的狀態
     */
    void update(String subject);
}

4.具體觀察者對象,實現更新的方法,使自身的狀態和目標的狀態保持一致ide

class ConcreteObserver implements Observer {

    @Override
    public void update(String newState) {
        //具體的更新實現
        //這裏可能須要更新觀察者的狀態,使其與目標的狀態保持一致
        System.out.println("接收到:" + newState);
    }
}

5.建立推模型客戶端,用於測試

public class PushClient {
    
    public static void main(String[] args) {
        //建立主題對象
        ConcreteSubject subject = new ConcreteSubject();
        //建立觀察者對象
        Observer observer = new ConcreteObserver();
        //將觀察者對象登記到主題對象上
        subject.attach(observer);
        //改變主題對象的狀態
        subject.change("push state");
    }
    
}

6.運行結果

接收到:push state

拉模式

1.定義目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口

class Subject {
    /**
     * 用來保存註冊的觀察者對象
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 註冊觀察者對象
     *
     * @param observer 觀察者對象
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 通知全部註冊的觀察者對象
     */
    public void notifyObservers() {
        for (Observer observer : observers) {
            // 注意這句代碼'
            observer.update(this);
        }
    }
}

2.具體的目標對象,負責把有關狀態存入到相應的觀察者對象,並在本身狀態發生改變時,通知各個觀察者

class ConcreteSubject extends Subject {
    /**
     * 示意,目標對象的狀態
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void change(String subjectState) {
        this.subjectState = subjectState;
        //狀態發生改變,通知各個觀察者
        this.notifyObservers();
    }
}

3.建立觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象

interface Observer {
    /**
     * 更新的接口
     *
     * @param subject 傳入目標對象,好獲取相應的目標對象的狀態
     */
    void update(Subject subject);
}

4.具體觀察者對象,實現更新的方法,使自身的狀態和目標的狀態保持一致

class ConcreteObserver implements Observer {
    /**
     * 示意,觀者者的狀態
     */
    private String observerState;

    @Override
    public void update(Subject subject) {
        //具體的更新實現
        //這裏可能須要更新觀察者的狀態,使其與目標的狀態保持一致
        observerState = ((ConcreteSubject) subject).getSubjectState();
        System.out.println("接收到:" + observerState);
    }
}

5.建立拉模型客戶端,用於測試

public class PullClient {

    public static void main(String[] args) {
        //建立主題對象
        ConcreteSubject subject = new ConcreteSubject();
        //建立觀察者對象
        Observer observer = new ConcreteObserver();
        //將觀察者對象登記到主題對象上
        subject.attach(observer);
        //改變主題對象的狀態
        subject.change("pull state");
    }

}

6.運行結果

接收到:pull state

上文說過推模型是假定主題對象知道觀察者須要的數據,這種模型下若是數據發生變動會形成極大的影響;而拉模型是主題對象不知道觀察者具體須要什麼數據,沒有辦法的狀況下,乾脆把自身傳遞給觀察者,讓觀察者本身去按須要取值。因而可知:拉模式的適用範圍更廣;

JDK中應用

對於觀察者模式,其實Java已經爲咱們提供了已有的接口和類。對於訂閱者(Subscribe,觀察者)Java爲咱們提供了一個接口。

UML圖

JDK自帶

在JAVA語言的 java.util 庫裏面,提供了一個Observable類以及一個Observer接口,構成JAVA語言對觀察者模式的支持。

Observer: 只定義了一個 update() 方法,當被觀察者對象的狀態發生變化時,被觀察者對象的 notifyObservers() 方法就會調用這一方法。

public interface Observer {
    void update(Observable o, Object arg);
}

Observable: 充當觀察目標類,在Observable中定義了一個向量Vector來存儲觀察者對象。一個觀察目標類能夠有多個觀察者對象,每一個觀察者對象都是實現Observer接口的對象。在被觀察者發生變化時,會調用ObservablenotifyObservers()方法,此方法調用全部的具體觀察者的update()方法, 從而使全部的觀察者都被通知更新本身。

  • setChanged() 設置一個內部標記變量,表明被觀察者對象的狀態發生了變化。
  • notifyObservers()調用全部登記過的觀察者對象的update()方法,使這些觀察者對象能夠更新本身。
public class Observable {
    private boolean changed = false; //是否改變狀態,每次都須要設置,表示內容發生變化
    private Vector<Observer> obs; //Vector利用同步方法來線程安全,線程安全在多線程狀況下不會形成數據混亂

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector<>();
    }


    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }


    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    //通知方法,用於在方法內部循環調用向量中每個觀察者的update()方法。
    public void notifyObservers() {
        notifyObservers(null);
    }


    public void notifyObservers(Object arg) {

        Object[] arrLocal;

        synchronized (this) {
            if (!changed) //狀態值未改變時返回,不通知
                return;
            arrLocal = obs.toArray(); //將Vector轉換成數組
            clearChanged(); //重置狀態
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }


    public synchronized int countObservers() {
        return obs.size();
    }
}

小案例

1.定義兩個實現了實現java.util.Observer接口的觀察者

class SubscribeReader implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("開始讀取:" + ((Publish) o).getMessage());
    }
}

class SubscribeWrite implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("開始寫入:" + ((Publish) o).getMessage());
    }
}

2.建立繼承java.util.Observable的通知者

class Publish extends Observable {
    private String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
        //改變通知者的狀態
        super.setChanged();
        //調用父類Observable方法,通知全部觀察者
        super.notifyObservers();
    }
}

3.建立測試客戶端

public class Client {

    public static void main(String[] args) {
        Publish publish = new Publish();
        // 遵循FIFO 模型 先進後出
        SubscribeWrite write = new SubscribeWrite();
        SubscribeReader reader = new SubscribeReader();
        publish.addObserver(reader);
        publish.addObserver(write);
        publish.setMessage("Hello Battcn");
        publish.setMessage("QQ:1837307557");
        publish.setMessage("Email:1837307557@qq.com");
    }

}

4.運行結果

開始寫入:Hello Battcn
開始讀取:Hello Battcn
開始寫入:QQ:1837307557
開始讀取:QQ:1837307557
開始寫入:Email:1837307557@qq.com
開始讀取:Email:1837307557@qq.com

觀察者模式與MVC

在當前流行的MVC(Model-View-Controller)架構中也應用了觀察者模式,MVC是一種架構模式,它包含三個角色:模型(Model),視圖(View)和控制器(Controller)。其中模型可對應於觀察者模式中的觀察目標,而視圖對應於觀察者,控制器可充當二者之間的中介者。當模型層的數據發生改變時,視圖層將自動改變其顯示內容。

總結

實現的關鍵是要創建觀察者和被觀察者之間的聯繫、好比在被觀察者類中有個集合是用於存放觀察者的、當被檢測的東西發生改變的時候就要通知全部觀察者。在被觀察者中要提供一些對全部觀察者管理的一些方法.目的是添加或者刪除一些觀察者.這樣才能讓被觀察者及時的通知觀察者關係的狀態已經改變、而且調用觀察者通用的方法將變化傳遞過去。

在實現觀察者模式,若是JDK的Observable類和一個Observer接口能知足需求,直接複用便可,無需本身編寫抽象觀察者、抽象主題類;

可是,java.util.Observable是一個類而不是接口,你必須設計一個類繼承它。若是某個類想同時具備Observable類和另外一個超類的行爲,因爲java不支持多重繼承。因此這個時候就須要本身實現一整套觀察者模式。

優勢

  • 可實現表示層和數據邏輯層的分離,定義了穩定的消息更新傳遞機制,並抽象了更新接口,使得能夠有各類各樣不一樣的表示層充當具體觀察者角色(Model/View)
  • 支持廣播通訊,觀察目標會向全部已註冊的觀察者對象發送通知,簡化了一對多系統設計的難度(Publish/Subscribe)
  • 實現動態聯動。因爲觀察者模式對觀察者註冊實行管理,那就能夠在運行期間,經過動態的控制註冊的觀察者,來控制某個動做的聯動範圍,從而實現動態聯動。

缺點

  • 若是一個被觀察者對象有不少直接和間接的觀察者,那麼將全部的觀察者都通知到會花費不少時間。
  • 若是在觀察者和被觀察者之間有循環依賴的話,被觀察者會觸發它們造成循環調用,可能致使系統崩潰。
  • 觀察者模式沒有相應的機制讓觀察者知道被觀察者對象是怎麼發生變化的,而僅僅只是知道被觀察者發生了變化。

觀察者模式是一種使用頻率很是高的設計模式,不管是移動應用、Web應用或者桌面應用,觀察者模式幾乎無處不在,它爲實現對象之間的聯動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的對象交互場景均可以使用觀察者模式。

說點什麼

參考文獻:http://www.cnblogs.com/JsonShare/p/7270546.html

全文代碼:https://gitee.com/battcn/design-pattern/tree/master/Chapter17/battcn-observer

  • 我的QQ:1837307557
  • battcn開源羣(適合新手):391619659

微信公衆號:battcn(歡迎調戲)

相關文章
相關標籤/搜索