六:觀察者模式

 定義:觀察者模式(有時又被稱爲發佈-訂閱模式、模型-視圖模式、源-收聽者模式或從屬者模式)是軟件設計模式的一種。在此種模式中,一個目標物件管理全部相依於它的觀察者物件,而且在它自己的狀態改變時主動發出通知。這一般透過呼叫各觀察者所提供的方法來實現。此種模式一般被用來實做事件處理系統。java

                 上面的定義當中,主要有這樣幾個意思,首先是有一個目標的物件,通俗點講就是一個類,它管理了全部依賴於它的觀察者物件,或者通俗點說是觀察者類,並在它本身狀態發生變化時,主動發出通知。編程

                 簡單點歸納成通俗的話來講,就是一個類管理着全部依賴於它的觀察者類,而且它狀態變化時會主動給這些依賴它的類發出通知。設計模式

                 那麼咱們針對上面的描述給出觀察者模式的類圖,百度百科沒有給出觀察者模式的類圖,這裏LZ本身使用工具給各位畫一個。數組

                 能夠看到,咱們的被觀察者類Observable只關聯了一個Observer的列表,而後在本身狀態變化時,使用notifyObservers方法通知這些Observer,具體這些Observer都是什麼,被觀察者是不關心也不須要知道的。併發

                上面就將觀察者和被觀察者兩者的耦合度降到很低了,而咱們具體的觀察者是必需要知道本身觀察的是誰,因此它依賴於被觀察者。工具

                下面LZ給寫出一個很簡單的觀察者模式,來使用JAVA代碼簡單詮釋一下上面的類圖。網站

                首先是觀察者接口。this

複製代碼
package net;

//這個接口是爲了提供一個統一的觀察者作出相應行爲的方法
public interface Observer {

    void update(Observable o);
    
}
複製代碼

                再者是具體的觀察者。spa

複製代碼
package net;

public class ConcreteObserver1 implements Observer{

    public void update(Observable o) {
        System.out.println("觀察者1觀察到" + o.getClass().getSimpleName() + "發生變化");
        System.out.println("觀察者1作出相應");
    }

}
複製代碼
複製代碼
package net;

public class ConcreteObserver2 implements Observer{

    public void update(Observable o) {
        System.out.println("觀察者2觀察到" + o.getClass().getSimpleName() + "發生變化");
        System.out.println("觀察者2作出相應");
    }

}
複製代碼

                下面是被觀察者,它有一個觀察者的列表,而且有一個通知全部觀察者的方法,通知的方式就是調用觀察者通用的接口行爲update方法。下面咱們看它的代碼。線程

複製代碼
package net;

import java.util.ArrayList;
import java.util.List;

public class Observable {

    List<Observer> observers = new ArrayList<Observer>();
    
    public void addObserver(Observer o){
        observers.add(o);
    }
    
    public void changed(){
        System.out.println("我是被觀察者,我已經發生變化了");
        notifyObservers();//通知觀察本身的全部觀察者
    }
    
    public void notifyObservers(){
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
}
複製代碼

                這裏面很簡單,新增兩個方法,一個是爲了改變本身的同時通知觀察者們,一個是爲了給客戶端一個添加觀察者的公共接口。

                下面咱們使用客戶端調用一下,看一下客戶端如何操做。

複製代碼
package net;


public class Client {

    public static void main(String[] args) throws Exception {
        Observable observable = new Observable();
        observable.addObserver(new ConcreteObserver1());
        observable.addObserver(new ConcreteObserver2());
        
        observable.changed();
    }
}
複製代碼

                 運行結果以下。

 

                 能夠看到咱們在操做被觀察者時,只要調用changed方法,觀察者們就會作出相應的動做,而添加觀察者這個行爲算是準備階段,將具體的觀察者關聯到被觀察者上面去。 

                下面LZ給出一個有實際意義的例子,好比咱們常常看的小說網站,都有這樣的功能,就是讀者能夠訂閱做者,這當中就有明顯的觀察者模式案例,就是做者和讀者。他們的關係是一旦讀者關注了一個做者,那麼這個做者一旦有什麼新書,就都要通知讀者們,這明顯是一個觀察者模式的案例,因此咱們可使用觀察者模式解決。

                 因爲JDK中爲了方便開發人員,已經寫好了現成的觀察者接口和被觀察者類,下面LZ先給出JDK中現成的觀察者和被觀察者代碼,外加本身的一點解釋,來幫助一些讀者對JDK中對觀察者模式的支持熟悉一下。

                 先來觀察者接口。

//觀察者接口,每個觀察者都必須實現這個接口
public interface Observer {
    //這個方法是觀察者在觀察對象產生變化時所作的響應動做,從中傳入了觀察的對象和一個預留參數
    void update(Observable o, Object arg);

}

                下面是被觀察者類。

複製代碼
import java.util.Vector;

//被觀察者類
public class Observable {
    //這是一個改變標識,來標記該被觀察者有沒有改變
    private boolean changed = false;
    //持有一個觀察者列表
    private Vector obs;
    
    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);
    }
    //notifyObservers(Object arg)的重載方法
    public void notifyObservers() {
    notifyObservers(null);
    }
    //通知全部觀察者,被觀察者改變了,你能夠執行你的update方法了。
    public void notifyObservers(Object arg) {
        //一個臨時的數組,用於併發訪問被觀察者時,留住觀察者列表的當前狀態,這種處理方式其實也算是一種設計模式,即備忘錄模式。
        Object[] arrLocal;
    //注意這個同步塊,它表示在獲取觀察者列表時,該對象是被鎖定的
    //也就是說,在我獲取到觀察者列表以前,不容許其餘線程改變觀察者列表
    synchronized (this) {
        //若是沒變化直接返回
        if (!changed)
                return;
            //這裏將當前的觀察者列表放入臨時數組
            arrLocal = obs.toArray();
            //將改變標識從新置回未改變
            clearChanged();
        }
        //注意這個for循環沒有在同步塊,此時已經釋放了被觀察者的鎖,其餘線程能夠改變觀察者列表
        //可是這並不影響咱們當前進行的操做,由於咱們已經將觀察者列表複製到臨時數組
        //在通知時咱們只通知數組中的觀察者,當前刪除和添加觀察者,都不會影響咱們通知的對象
        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();
    }
}
複製代碼

                 被觀察者除了一點同步的地方須要特殊解釋一下,其他的相信各位都能看明白各個方法的用途。其實上述JDK的類是有漏洞的,或者說,在咱們使用觀察者模式時要注意一個問題,就是notifyObservers這個方法中的這一段代碼。

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

                 在循環遍歷觀察者讓觀察者作出響應時,JDK沒有去抓取update方法中的異常,因此假設在這過程當中有一個update方法拋出了異常,那麼剩下還未通知的觀察者就全都通知不到了,因此LZ我的比較疑惑這樣的用意(LZ沒法想象JAVA類庫的製造者沒考慮到這個問題),是sun當時真的忘了考慮這一點,仍是另有它意?固然各位讀者若是有本身的看法能夠告知LZ,不過LZ認爲,不論是sun如此作是別有用意,仍是真的欠考慮,咱們都要注意在update方法裏必定要處理好異常,我的以爲JDK中比較保險的作法仍是以下這樣。

for (int i = arrLocal.length-1; i>=0; i--){
            try {
                ((Observer)arrLocal[i]).update(this, arg);
            } catch (Throwable e) {e.printStackTrace();}
        }

                 這樣不管其中任何一個update是否成功都不會影響其他的觀察者進行更新狀態,咱們本身比較保險的作法就是給update方法整個加上try塊,或者確認不會發生運行時異常。

 

                 上面LZ和各位一塊兒分析了JDK中觀察者模式的源碼,下面咱們就拿上述小說網的例子,作一個DEMO。

                 首先要搞清楚在讀者和做者之間是誰觀察誰,很明顯,應該是讀者觀察做者。因此做者是被觀察者,讀者是觀察者,除了這兩個類以外,咱們還須要額外添加一個管理器幫咱們管理下做者的列表便於讀者關注,因而一個觀察者模式的DEMO就出現了。以下,首先是讀者類,LZ在各個類都加了點註釋。

複製代碼
//讀者類,要實現觀察者接口
public class Reader implements Observer{
    
    private String name;
    
    public Reader(String name) {
        super();
        this.name = name;
    }

    public String getName() {
        return name;
    }
    
    //讀者能夠關注某一位做者,關注則表明把本身加到做者的觀察者列表裏
    public void subscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).addObserver(this);
    }
    
    //讀者能夠取消關注某一位做者,取消關注則表明把本身從做者的觀察者列表裏刪除
    public void unsubscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
    }
    
    //當關注的做者發表新小說時,會通知讀者去看
    public void update(Observable o, Object obj) {
        if (o instanceof Writer) {
            Writer writer = (Writer) o;
            System.out.println(name+"知道" + writer.getName() + "發佈了新書《" + writer.getLastNovel() + "》,非要去看!");
        }
    }
    
}
複製代碼

                       下面是做者類。

複製代碼
//做者類,要繼承自被觀察者類
public class Writer extends Observable{
    
    private String name;//做者的名稱
    
    private String lastNovel;//記錄做者最新發布的小說

    public Writer(String name) {
        super();
        this.name = name;
        WriterManager.getInstance().add(this);
    }

    //做者發佈新小說了,要通知全部關注本身的讀者
    public void addNovel(String novel) {
        System.out.println(name + "發佈了新書《" + novel + "》!");
        lastNovel = novel;
        setChanged();
        notifyObservers();
    }
    
    public String getLastNovel() {
        return lastNovel;
    }

    public String getName() {
        return name;
    }

}
複製代碼

                 而後咱們還須要一個管理器幫咱們管理這些做者。以下。

複製代碼
import java.util.HashMap;
import java.util.Map;

//管理器,保持一份獨有的做者列表
public class WriterManager{
    
    private Map<String, Writer> writerMap = new HashMap<String, Writer>();

    //添加做者
    public void add(Writer writer){
        writerMap.put(writer.getName(), writer);
    }
    //根據做者姓名獲取做者
    public Writer getWriter(String name){
        return writerMap.get(name);
    }
    
    //單例
    private WriterManager(){}
    
    public static WriterManager getInstance(){
        return WriterManagerInstance.instance;
    }
    private static class WriterManagerInstance{
        
        private static WriterManager instance = new WriterManager();
        
    }
}
複製代碼

                好了,這下咱們的觀察者模式就作好了,這個簡單的DEMO能夠支持讀者關注做者,看成者發佈新書時,讀者會觀察到這個事情,會產生相應的動做。下面咱們寫個客戶端調用一下。

複製代碼
//客戶端調用
public class Client {

    public static void main(String[] args) {
        //假設四個讀者,兩個做者
        Reader r1 = new Reader("謝廣坤");
        Reader r2 = new Reader("趙四");
        Reader r3 = new Reader("七哥");
        Reader r4 = new Reader("劉能");
        Writer w1 = new Writer("謝大腳");
        Writer w2 = new Writer("王小蒙");
        //四人關注了謝大腳
        r1.subscribe("謝大腳");
        r2.subscribe("謝大腳");
        r3.subscribe("謝大腳");
        r4.subscribe("謝大腳");
        //七哥和劉能還關注了王小蒙
        r3.subscribe("王小蒙");
        r4.subscribe("王小蒙");
        
        //做者發佈新書就會通知關注的讀者
        //謝大腳寫了設計模式
        w1.addNovel("設計模式");
        //王小蒙寫了JAVA編程思想
        w2.addNovel("JAVA編程思想");
        //謝廣坤取消關注謝大腳
        r1.unsubscribe("謝大腳");
        //謝大腳再寫書將不會通知謝廣坤
        w1.addNovel("觀察者模式");
    }
    
}
複製代碼

                    看下咱們獲得的結果,就會發現,咱們確實通知了讀者它所關注的做者的動態,並且讀者取消關注之後,做者的動態將再也不通知該讀者。下面是運行結果。

                咱們使用觀察者模式的用意是爲了做者再也不須要關心他發佈新書時都要去通知誰,更重要的是他不須要關心他通知的是讀者仍是其它什麼人,他只知道這我的是實現了觀察者接口的,即咱們的被觀察者依賴的只是一個抽象的接口觀察者接口,而不關心具體的觀察者都有誰都是什麼,好比之後要是遊客也能夠關注做者了,那麼只要遊客類實現觀察者接口,那麼同樣能夠將遊客列入到做者的觀察者列表中。

相關文章
相關標籤/搜索