(六) 觀察者模式

來源:互聯網
上載者:User

標籤:sdn   需要   ade   rman   bool   title   需求   public   維護   

轉載:http://www.cnblogs.com/zuoxiaolong/p/pattern7.html 

本章我們討論一個除前面的單例以及代理模式之外,一個WEB項目中有可能用到的設計模式,即觀察者模式。

                 說起觀察者模式,LZ還是非常激動的,當初這算是第一個讓LZ感受到設計模式強大的傢伙。當初LZ要做一個小型WEB項目,要上傳給伺服器檔案,一個需求就是要顯示上傳進度,LZ就是用這個模式解決了當時的問題,那時LZ接觸JAVA剛幾個月,比葫蘆畫瓢的用了觀察者模式。

                 現在談及觀察者模式,能用到的地方就相對較多了,通常意義上如果一個對象狀態的改變需要通知很多對這個對象關注的一系列對象,就可以使用觀察者模式。

                 下面LZ先給出觀察者模式標準版的定義,引自百度百科。

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

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

                 簡單點概括成通俗的話來說,就是一個類管理著所有依賴於它的觀察者類,並且它狀態變化時會主動給這些依賴它的類發出通知。

                 那麼我們針對上面的描述給出觀察者模式的類圖,百度百科沒有給出觀察者模式的類圖,這裡LZ自己使用工具給各位畫一個。

                 可以看到,我們的被觀察者類Observable只關聯了一個Observer的列表,然後在自己狀態變化時,使用notifyObservers方法通知這些Observer,具體這些Observer都是什麼,被觀察者是不關心也不需要知道的。

                上面就將觀察者和被觀察者二者的耦合度降到很低了,而我們具體的觀察者是必須要知道自己觀察的是誰,所以它依賴於被觀察者。

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

                首先是觀察者介面。

package net;//這個介面是為了提供一個統一的觀察者做出相應行為的方法public interface Observer {    void update(Observable o);    }

                再者是具體的觀察者。

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("觀察者模式");    }    }

                    看下我們得到的結果,就會發現,我們確實通知了讀者它所關注的作者的動態,而且讀者取消追蹤以後,作者的動態將不再通知該讀者。下面是運行結果。

                我們使用觀察者模式的用意是為了作者不再需要關心他發布新書時都要去通知誰,更重要的是他不需要關心他通知的是讀者還是其它什麼人,他只知道這個人是實現了觀察者介面的,即我們的被觀察者依賴的只是一個抽象的介面觀察者介面,而不關心具體的觀察者都有誰都是什麼,比如以後要是遊客也可以關注作者了,那麼只要遊客類實現觀察者介面,那麼一樣可以將遊客列入到作者的觀察者列表中。

                另外,我們讓讀者自己來選擇自己關注的對象,這相當於被觀察者將維護通知對象的職能轉化給了觀察者,這樣做的好處是由於一個被觀察者可能有N多觀察者,所以讓被觀察者自己維護這個列表會很艱難,這就像一個老師被許多學生認識,那麼是所有的學生都記住老師的名字簡單,還是讓老師記住N多學生的名字簡單?答案顯而易見,讓學生們都記住一個老師的名字是最簡單的。

(六) 觀察者模式

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.