Java設計模式——觀察者模式(Observer Pattern)

場景一

描述:《孫子兵法》有云:“知彼知己,百戰不殆;不知彼而知己,一勝一負;不 知彼,不知己,每戰必殆”,那怎麼才能知己知彼呢?知己是很容易的,自己的軍隊嘛,很容易知道,那怎麼知彼呢?安插間諜是很好的一個辦法,我們今天就來講一個間諜的故事。

        韓非子大家都應該記得吧,法家的代表人物,主張建立法制社會,實施重罰制度,真是非常有遠見呀,看看現在社會在呼籲什麼,建立法制化的社會,在 2000 多年前就已經提出了。大家可能還不知道,法家還有一個非常重要的代表人物,李斯,對,就是李斯,秦國的丞相,最終被殘忍的車裂的那位,李斯和韓非子都是荀子的學生,李斯是師兄,韓非子是師弟,若干年後,李斯成爲最強諸侯秦國的上尉,致力於統一全國,於是安插了間諜到各個國家的重要人物的身邊,以獲取必要的信息,韓非子作爲韓國的重量級人物,身邊自然沒少間諜了,韓非子早飯吃的什麼,中午放了幾個 P,晚上在做什麼娛樂,李斯都瞭如指掌,那可是相隔千里!怎麼做到的呢?間諜呀! 好,我們先通過程序把這個過程展現一下,看看李斯是怎麼監控韓非子,先看類圖:

看慣了清一色的談黃色類圖,咱換個顏色,寫程序是一個藝術創作過程,我的一個同事就曾經把一個類圖畫成一個小烏龜的形狀,超級牛 X。這個類圖應該是程序員最容易想到得,你要監控,我就給你監控,正確呀,我們來看程序的實現,先看我們的主角韓非子的接口(類似於韓非子這樣的人,被觀察者角色):

package com.gumx.common;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 類似韓非子這花樣的人,被監控起來了還不知道
*/
public interface IHanFeiZi {
    //韓非子也是人,也要吃早飯的
    public void haveBreakfast();
    //韓非之也是人,是人就要娛樂活動,至於活動時啥,嘿嘿,不說了
    public void haveFun();
}

然後看韓非子的實現類 HanFeiZi.java:

package com.gumx.common;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 韓非子,李斯的師弟,韓國的重要人物
*/
public class HanFeiZi implements IHanFeiZi{
    //韓非子是否在吃飯,作爲監控的判斷標準
    private boolean isHaveBreakfast = false;
    //韓非子是否在娛樂
    private boolean isHaveFun = false;

    //韓非子要吃飯了
    public void haveBreakfast(){
        System.out.println("韓非子:開始吃飯了...");
        this.isHaveBreakfast =true;
    }

    //韓非子開始娛樂了,古代人沒啥娛樂,你能想到的就那麼多
    public void haveFun(){
        System.out.println("韓非子:開始娛樂了...");
        this.isHaveFun = true;
    }

    //以下是bean的基本方法,getter/setter,不多說
    public boolean isHaveBreakfast() {
        return isHaveBreakfast;
    }

    public void setHaveBreakfast(boolean isHaveBreakfast) {
        this.isHaveBreakfast = isHaveBreakfast;
    }

    public boolean isHaveFun() {
        return isHaveFun;
    }

    public void setHaveFun(boolean isHaveFun) {
        this.isHaveFun = isHaveFun;
    }
}

其中有兩個 getter/setter 方法,這個就沒有在類圖中表示出來,比較簡單,通過 isHaveBreakfast和 isHaveFun 這兩個布爾型變量來判斷韓非子是否在吃飯或者娛樂,韓非子是屬於被觀察者,那還有觀察者李斯,我們來看李斯這類人的接口:

package com.gumx.common;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 類似於李斯的這種人,現代嘛叫做偷窺狂
*/
public interface ILiSi {
    //一發現別人有動靜,自己也要行動起來
    public void update(String context);
}

李斯這類人比較簡單,一發現自己觀察的對象發生了變化,比如吃飯,娛樂了,自己立刻也要行動起來,那怎麼行動呢?看實現類:

package com.gumx.common;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 李斯這個人,是個觀察者,只要韓非子一有動靜,這邊就知道
*/
public class LiSi implements ILiSi{
        //首先李斯是個觀察者,一旦韓非子有活動,他就知道,他就要向老闆彙報
        public void update(String str){
        System.out.println("李斯:觀察到韓非子活動,開始向老闆彙報了...");
        this.reportToQiShiHuang(str);
        System.out.println("李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...\n");
    }

    //彙報給秦始皇
    private void reportToQiShiHuang(String reportContext){
        System.out.println("李斯:報告,秦老闆!韓非子有活動了--->"+reportContext);
    }
}

韓非子是秦始皇非常崇拜的人物,甚至說過見韓非子一面死又何憾!不過,韓非子還真是被秦始皇幹掉的,歷史呀上演過太多這樣的悲劇。這麼重要的人物有活動,你李斯敢不向老大彙報?!兩個重量級的人物都定義出來了,那我們就來看看要怎麼監控,先寫個監控程序:

package com.gumx.common;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 監控程序
*/
class Watch extends Thread{
    private HanFeiZi hanFeiZi;
    private LiSi liSi;
    private String type;

    //通過構造函數傳遞參數,我要監控的是誰,誰來監控,要監控什麼
    public Watch(HanFeiZi _hanFeiZi,LiSi _liSi,String _type){
        this.hanFeiZi =_hanFeiZi;
        this.liSi = _liSi;
        this.type = _type;
    }

    @Override
    public void run(){
        while(true){
            if(this.type.equals("breakfast")){ //監控是否在吃早餐
                //如果發現韓非子在吃飯,就通知李斯
                if(this.hanFeiZi.isHaveBreakfast()){
                    this.liSi.update("韓非子在吃飯");
                    //重置狀態,繼續監控
                    this.hanFeiZi.setHaveBreakfast(false);
                }
            }else{//監控是否在娛樂
                if(this.hanFeiZi.isHaveFun()){
                    this.liSi.update("韓非子在娛樂");
                    this.hanFeiZi.setHaveFun(false);
                }
            }
        }
    }
}

監控程序繼承了 java.lang.Thread 類,可以同時啓動多個線程進行監控,Java 的多線程機制還是比較簡單的,繼承 Thread 類,重寫 run()方法,然後 new SubThread(),再然後 subThread.start()就可以啓動一個線程了,我們繼續往下看:

package com.gumx.common;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 這個Client就是我們,用我們的視角看待這段歷史
*/
public class Client {

    public static void main(String[] args) throws InterruptedException {
        //定義出韓非子和李斯
        LiSi liSi = new LiSi();
        HanFeiZi hanFeiZi = new HanFeiZi();

        //觀察早餐
        Watch watchBreakfast = new Watch(hanFeiZi,liSi,"breakfast");
        //開始啓動線程,監控
        watchBreakfast.start();

        //觀察娛樂情況
        Watch watchFun = new Watch(hanFeiZi,liSi,"fun");
        watchFun.start();
        //然後這裏我們看看韓非子在幹什麼
        Thread.sleep(1000); //主線程等待1秒後後再往下執行
        hanFeiZi.haveBreakfast();

        //韓非子娛樂了
        Thread.sleep(1000);
        hanFeiZi.haveFun();
    }
}

運行結果如下:

韓非子:開始吃飯了...

李斯:觀察到李斯活動,開始向老闆彙報了...

李斯:報告,秦老闆!韓非子有活動了--->韓非子在吃飯

李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...

韓非子:開始娛樂了...

李斯:觀察到李斯活動,開始向老闆彙報了...

李斯:報告,秦老闆!韓非子有活動了--->韓非子在娛樂


李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...

          結果出來,韓非子一吃早飯李斯就知道,韓非子一娛樂李斯也知道,非常正確!結果正確但並不表示你有成績,我告訴你:你的成績是 0,甚至是負的,你有沒有看到你的 CPU 飆升,Eclipse 不響應狀態?看到了?看到了你還不想爲什麼?!看看上面的程序,別的就不多說了,使用了一個 while(true)這樣一個死循環來做監聽,你要是用到項目中,你要多少硬件投入進來?你還讓不讓別人的程序也 run 起來?!一臺服務器就跑你這一個程序就完事了,錯,絕對的錯!

         錯誤也看到了,我們必須要修改,這個沒有辦法應用到項目中去呀,而且這個程序根本就不是面向對象的程序,這完全是面向過程的(我寫出這樣的程序也不容易呀,安慰一下自己),不改不行,怎麼修改呢?我們來想,既然韓非子一吃飯李斯就知道了,那我們爲什麼不把李斯這個類聚集到韓非子這裏類上呢?說改就改,立馬動手,我們來看修改後的類圖:

類圖非常簡單,就是在 HanFeiZi 類中引用了 IliSi 這個接口,看我們程序代碼怎麼修改,IhanFeiZi接口完全沒有修改,我們來看 HanFeiZi 這個實現類:

package com.gumx.advance;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 韓非子,李斯的師弟,韓國的重要人物
*/
public class HanFeiZi implements IHanFeiZi{
   //把李斯聲明出來
    private ILiSi liSi =new LiSi();

    //韓非子要吃飯了
    public void haveBreakfast(){
        System.out.println("韓非子:開始吃飯了...");
        //通知李斯
        this.liSi.update("韓非子在吃飯");
    }

    //韓非子開始娛樂了,古代人沒啥娛樂,你能想到的就那麼多
    public void haveFun(){
        System.out.println("韓非子:開始娛樂了...");
        this.liSi.update("韓非子在娛樂");
    }
}

韓非子 HanFeiZi 實現類就把接口的兩個方法實現就可以了,在每個方法中調用 LiSi.update()方法,完成李斯觀察韓非子任務,李斯的接口和實現類都沒有任何改變,我們再來看看 Client 程序的變更:

package com.gumx.advance;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 這個Client就是我們,用我們的視角看待這段歷史
*/
public class Client {

    public static void main(String[] args) {
        //定義出韓非子
        HanFeiZi hanFeiZi = new HanFeiZi();

        //然後這裏我們看看韓非子在幹什麼
        hanFeiZi.haveBreakfast();

        //韓非子娛樂了
        hanFeiZi.haveFun();
    }
}

李斯都不用在 Client 中定義了,非常簡單,運行結果如下:

韓非子:開始吃飯了...

李斯:觀察到韓非子活動,開始向老闆彙報了...

李斯:報告,秦老闆!韓非子有活動了--->韓非子在吃飯

李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...

韓非子:開始娛樂了...

李斯:觀察到韓非子活動,開始向老闆彙報了...

李斯:報告,秦老闆!韓非子有活動了--->韓非子在娛樂


李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...

運行結果正確,效率也比較高,是不是應該樂呵樂呵了?大功告成了?稍等等,你想在戰國爭雄的時候,韓非子這麼有名望(法家代表)、有實力(韓國的公子,他老爹參與過爭奪韓國王位)的人,就只有秦國一個國家關心他嗎?想想也不可能呀,肯定有一大幫的各國的類似李斯這樣的人在看着他,監視着一舉一動,但是看看我們的程序,你在 HanFeiZi 這個類中定義:

        private ILiSi liSi =new LiSi();

         一下子就敲死了,只有李斯才能觀察到韓非子,這是不對的,也就是說韓非子的活動只通知了李斯一個人,這不可能;再者,李斯只觀察韓非子的吃飯,娛樂嗎?政治傾向不關心嗎?思維傾向不關心嗎?殺人放火不關心嗎?也就說韓非子的一系列活動都要通知李斯,那可怎麼辦?要按照上面的例子,我們不是要修改瘋掉了嗎?這和開閉原則嚴重違背呀,我們的程序有問題,怎麼修改,來看類圖:


        我們把接口名稱修改了一下,這樣顯得更抽象化,Observable 是被觀察者,就是類似韓非子這樣的人,Observer 接口是觀察者,類似李斯這樣的,同時還有其他國家的比如王斯、劉斯等,在 Observable 接口中有三個比較重要的方法,分別是 addObserver 增加觀察者,deleteObserver 刪除觀察者,notifyObservers通知所有的觀察者,這是什麼意思呢?我這裏有一個信息,一個對象,我可以允許有多個對象來察看,你觀察也成,我觀察也成,只要是觀察者就成,也就是說我的改變或動作執行,會通知其他的對象,看程序會更明白一點,先看 Observable 接口:

package com.gumx.advance2;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 所有被觀察者者,通用接口
*/
public interface Observable {

    //增加一個觀察者
    public void addObserver(Observer observer);

    //刪除一個觀察者,——我不想讓你看了
    public void deleteObserver(Observer observer);

    //既然要觀察,我發生改變了他也應該用所動作——通知觀察者
    public void notifyObservers(String context);
}

這是一個通用的被觀察者接口,所有的被觀察者都可以實現這個接口。再來看韓非子的實現類:

package com.gumx.advance2;

import java.util.ArrayList;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 韓非子,李斯的師弟,韓國的重要人物
*/
public class HanFeiZi implements Observable{
    //定義個變長數組,存放所有的觀察者
    private ArrayList<Observer> observerList = new ArrayList<Observer>();

    //增加觀察者
    public void addObserver(Observer observer){
        this.observerList.add(observer);
    }

    //刪除觀察者
    public void deleteObserver(Observer observer){
        this.observerList.remove(observer);
    }

    //通知所有的觀察者
    public void notifyObservers(String context){
        for(Observer observer:observerList){
            observer.update(context);
        }
    }

    //韓非子要吃飯了
    public void haveBreakfast(){
        System.out.println("韓非子:開始吃飯了...");
        //通知所有的觀察者
        this.notifyObservers("韓非子在吃飯");
    }

    //韓非子開始娛樂了,古代人沒啥娛樂,你能想到的就那麼多
    public void haveFun(){
        System.out.println("韓非子:開始娛樂了...");
        this.notifyObservers("韓非子在娛樂");
    }
}

再來看觀察者接口 Observer.java:

package com.cbf4life.advance2;

 

/**

* @author cbf4Life [email protected]

* I'm glad to share my knowledge with you all.

* 所有觀察者,通用接口

*/

public interface Observer {

 

//一發現別人有動靜,自己也要行動起來

public void update(String context);

}

 

 

然後是三個很無恥的觀察者,偷窺狂嘛:

package com.gumx.advance2;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 所有觀察者,通用接口
*/
public interface Observer {

    //一發現別人有動靜,自己也要行動起來
    public void update(String context);
}

然後是三個很無恥的觀察者,偷窺狂嘛:

package com.gumx.advance2;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 李斯這個人,是個觀察者,只要韓非子一有動靜,這邊就知道
*/
public class LiSi implements Observer{

    //首先李斯是個觀察者,一旦韓非子有活動,他就知道,他就要向老闆彙報
    public void update(String str){
        System.out.println("李斯:觀察到李斯活動,開始向老闆彙報了...");
        this.reportToQiShiHuang(str);
        System.out.println("李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...\n");
    }

    //彙報給秦始皇
    private void reportToQiShiHuang(String reportContext){
        System.out.println("李斯:報告,秦老闆!韓非子有活動了--->"+reportContext);
    }
}

李斯是真有其人,以下兩個觀察者是杜撰出來的:

package com.gumx.advance2;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 王斯,也是觀察者,杜撰的人名
*/
public class WangSi implements Observer{

    //王斯,看到韓非子有活動,自己就受不了
    public void update(String str){
        System.out.println("王斯:觀察到韓非子活動,自己也開始活動了...");
        this.cry(str);
        System.out.println("王斯:真真的哭死了...\n");
    }

    //一看李斯有活動,就哭,痛哭
    private void cry(String context){
        System.out.println("王斯:因爲"+context+",——所以我悲傷呀!");
    }
}


package com.gumx.advance2;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 劉斯這個人,是個觀察者,只要韓非子一有動靜,這邊就知道
* 杜撰的人名
*/
public class LiuSi implements Observer{

    //劉斯,觀察到韓非子活動後,自己也做一定得事情
    public void update(String str){
        System.out.println("劉斯:觀察到韓非子活動,開始動作了...");
        this.happy(str);
        System.out.println("劉斯:真被樂死了\n");
    }

    //一看韓非子有變化,他就快樂
    private void happy(String context){
        System.out.println("劉斯:因爲" +context+",——所以我快樂呀!" );
    }
}

所有的歷史人物都在場了,那我們來看看這場歷史鬧劇是如何演繹的:

package com.gumx.advance2;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 這個Client就是我們,用我們的視角看待這段歷史
*/
public class Client {

    public static void main(String[] args) {
        //三個觀察者產生出來
        Observer liSi = new LiSi();
        Observer wangSi = new WangSi();
        Observer liuSi = new LiuSi();

        //定義出韓非子
        HanFeiZi hanFeiZi = new HanFeiZi();

        //我們後人根據歷史,描述這個場景,有三個人在觀察韓非子
        hanFeiZi.addObserver(liSi);
        hanFeiZi.addObserver(wangSi);
        hanFeiZi.addObserver(liuSi);

        //然後這裏我們看看韓非子在幹什麼
        hanFeiZi.haveBreakfast();
    }
}

運行結果如下:

韓非子:開始吃飯了...

李斯:觀察到李斯活動,開始向老闆彙報了...

李斯:報告,秦老闆!韓非子有活動了--->韓非子在吃飯

李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...

王斯:觀察到韓非子活動,自己也開始活動了...

王斯:因爲韓非子在吃飯,——所以我悲傷呀!

王斯:真真的哭死了...

劉斯:觀察到韓非子活動,開始動作了...

劉斯:因爲韓非子在吃飯,——所以我快樂呀!

劉斯:真被樂死了

好了,結果也正確了,也符合開閉原則了,也同時實現類間解耦,想再加觀察者?好呀,繼續實現Observer 接口就成了,這時候必須修改 Client 程序,因爲你業務都發生了變化。細心的你可能已經發現,HanFeiZi 這個實現類中應該抽象出一個父類,父類完全實現接口,HanFeiZi這個類只實現兩個方法 haveBreakfast  haveFun 就可以了,是的,是的,確實是應該這樣,那先稍等等,我    JDK             Observable            JDK     :

java.util.Observable     java.util.Observer                   Observable 接口可以改換成 java.util.Observale 實現類了,看如下類圖:


是不是又簡單了很多?那就對了!然後我們看一下我們程序的變更,先看 HanFeiZi 的實現類:

package com.gumx.perfect;

import java.util.ArrayList;
import java.util.Observable;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 韓非子,李斯的師弟,韓國的重要人物
*/
public class HanFeiZi extends Observable{

    //韓非子要吃飯了
    public void haveBreakfast(){
        System.out.println("韓非子:開始吃飯了...");
        //通知所有的觀察者
        super.setChanged();
        super.notifyObservers("韓非子在吃飯");
    }

    //韓非子開始娛樂了,古代人沒啥娛樂,你能想到的就那麼多
    public void haveFun(){
        System.out.println("韓非子:開始娛樂了...");
        super.setChanged();
        this.notifyObservers("韓非子在娛樂");
    }
}

改變的不多,引入了一個 java.util.Observable 對象,刪除了增加、刪除觀察者的方法,簡單了很多,那我們再來看觀察者的實現類:

package com.gumx.perfect;

import java.util.Observable;
import java.util.Observer;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 李斯這個人,是個觀察者,只要韓非子一有動靜,這邊就知道
*/
public class LiSi implements Observer{

    //首先李斯是個觀察者,一旦韓非子有活動,他就知道,他就要向老闆彙報
    public void update(Observable observable,Object obj){
        System.out.println("李斯:觀察到李斯活動,開始向老闆彙報了...");
        this.reportToQiShiHuang(obj.toString());
        System.out.println("李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...\n");
    }

    //彙報給秦始皇
    private void reportToQiShiHuang(String reportContext){
       System.out.println("李斯:報告,秦老闆!韓非子有活動了--->"+reportContext);
    }
}

就改變了黃色的部分,應該 java.util.Observer 接口要求 update 傳遞過來兩個變量,Observable 這個變量我們沒用到,就不處理了。其他兩個觀察者實現類也是相同的改動,如下代碼:

package com.gumx.perfect;
import java.util.Observable;
import java.util.Observer;
/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 王斯,也是觀察者,杜撰的人名
*/
public class WangSi implements Observer{

    //王斯,看到韓非子有活動,自己就受不了
    public void update(Observable observable,Object obj){
        System.out.println("王斯:觀察到韓非子活動,自己也開始活動了...");
        this.cry(obj.toString());
        System.out.println("王斯:真真的哭死了...\n");
    }

    //一看李斯有活動,就哭,痛哭
    private void cry(String context){
        System.out.println("王斯:因爲"+context+",——所以我悲傷呀!");
    }
}
 
package com.gumx.perfect;

import java.util.Observable;
import java.util.Observer;

/**
* @author gumx
* I'm glad to share my knowledge with you all.
* 劉斯這個人,是個觀察者,只要韓非子一有動靜,這邊就知道
* 杜撰的人名
*/
public class LiuSi implements Observer{

    //劉斯,觀察到韓非子活動後,自己也做一定得事情
    public void update(Observable observable,Object obj){
        System.out.println("劉斯:觀察到韓非子活動,開始動作了...");
        this.happy(obj.toString());
        System.out.println("劉斯:真被樂死了\n");
    }

    //一看韓非子有變化,他就快樂
    private void happy(String context){
        System.out.println("劉斯:因爲" +context+",——所以我快樂呀!" );
    }
}

然後再來看 Client 程序:

package com.gumx.perfect;

import java.util.Observer;

/**
* @author cbf4Life [email protected]
* I'm glad to share my knowledge with you all.
* 這個Client就是我們,用我們的視角看待這段歷史
*/
public class Client {

    public static void main(String[] args) {
        //三個觀察者產生出來
        Observer liSi = new LiSi();
        Observer wangSi = new WangSi();
        Observer liuSi = new LiuSi();

        //定義出韓非子
        HanFeiZi hanFeiZi = new HanFeiZi();

        //我們後人根據歷史,描述這個場景,有三個人在觀察韓非子
        hanFeiZi.addObserver(liSi);
        hanFeiZi.addObserver(wangSi);
        hanFeiZi.addObserver(liuSi);
  
        //然後這裏我們看看韓非子在幹什麼
        hanFeiZi.haveBreakfast();
   }
}

程序體內沒有任何變更,只是引入了一個接口而已,運行結果如下:

韓非子:開始吃飯了...

劉斯:觀察到韓非子活動,開始動作了...

劉斯:因爲韓非子在吃飯,——所以我快樂呀!

劉斯:真被樂死了

王斯:觀察到韓非子活動,自己也開始活動了...

王斯:因爲韓非子在吃飯,——所以我悲傷呀!

王斯:真真的哭死了...

李斯:觀察到李斯活動,開始向老闆彙報了...

李斯:報告,秦老闆!韓非子有活動了--->韓非子在吃飯

李斯:彙報完畢,秦老闆賞給他兩個蘿蔔吃吃...

運行結果一樣,只是通知的先後順序不同而已,程序已經簡約到極致了。以上講解的就是觀察者模式,這個模式的通用類圖如下:


        觀察者模式在實際項目的應用中非常常見,比如你到 ATM 機器上取錢,多次輸錯密碼,卡就會被 ATM吞掉,吞卡動作發生的時候,會觸發哪些事件呢?第一攝像頭連續快拍,第二,通知監控系統,吞卡發生;第三,初始化 ATM 機屏幕,返回最初狀態,你不能因爲就吞了一張卡,整個 ATM 都不能用了吧,一般前兩個動作都是通過觀察者模式來完成的。

         觀察者模式有一個變種叫做發佈/訂閱模型(Publish/Subscribe),如果你做過 EJB(EnterpriseJavaBean)的開發,這個你絕對不會陌生。EJB2 是個折騰死人不償命的玩意兒,寫個 Bean 要實現,還要繼承,再加上那一堆的配置文件,小項目還湊活,你要知道用 EJB 開發的基本上都不是小項目,到最後是每個項目成員都在罵 EJB 這個忽悠人的東西;但是 EJB3 是個非常優秀的框架,還是算比較輕量級,寫個 Bean只要加個 Annotaion 就成了,配置文件減少了,而且也引入了依賴注入的概念,雖然只是 EJB2 的翻版,但是畢竟還是前進了一步,不知道以後 EJB 的路會怎麼樣。在 EJB 中有三個類型的 Bean: Session Bean、EntityBean  MessageDriven Bean,我們這裏來說一下MessageDriven Bean(一般簡稱爲 MDB),消息驅動 Bean,消息的發佈者(Provider)發佈一個消息,也就是一個消息驅動 Bean,通過 EJB 容器(一般是 Message Queue消息隊列)通知訂閱者做出迴應,從原理上看很簡單,就是觀察者模式的升級版。

        那觀察者模式在什麼情況下使用呢?觀察者可以實現消息的廣播,一個消息可以觸發多個事件,這是觀察者模式非常重要的功能。使用觀察者模式也有兩個重點問題要解決:

        播鏈的問題。如果你做過數據庫的觸發器,你就應該知道有一個觸發器鏈的問題,比如表 A 上寫了一個觸發器,內容是一個字段更新後更新表 B 的一條數據,而表 B 上也有個觸發器,要更新表 C,表 C 也有觸發器…,完蛋了,這個數據庫基本上就毀掉了!我們的觀察者模式也是一樣的問題,一個觀察者可以有雙重身份,即使觀察者,也是被

觀察者,這沒什麼問題呀,但是鏈一旦建立,這個邏輯就比較複雜,可維護性非常差,根據經驗建議,在一個觀察者模式中最多出現一個對象既是觀察者也是被觀察者,也就是說消息最多轉發一次(傳遞兩次),這還是比較好控制的;

        異步處理問題。這個 EJB 是一個非常好的例子,被觀察者發生動作了,觀察者要做出迴應,如果觀察者比較多,而且處理時間比較長怎麼辦?那就用異步唄,異步處理就要考慮線程安全和隊列的問題,這個大家有時間看看 Message Queue,就會有更深的瞭解。

      

         我們在來回顧一下我們寫的程序,觀察者增加了,我就必須修改業務邏輯 Client 程序,這個是必須得嗎?回顧一下我們以前講到工廠方法模式的時候用到了 ClassUtils 這個類,其中有一個方法就是根據接口查找到所有的實現類,問題解決了吧!我可以查找到所有的觀察者,然後全部加進來,以後要是新增加觀察者也沒有問題呀,程序那真是一點都不用改了!




 

 






























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章