第22章 觀察者模式
22.1 韓非子身邊的臥底是誰派來的
《孫子兵法》有云:“知彼知己,百戰不殆;不知彼而知己,一勝一負;不知彼,不知己,每戰必殆”,那怎么才能知己知彼呢?知己是很容易的,自己的軍隊嘛,很容易知根知底,那怎么知彼呢?安插間諜是個好辦法,這是古今中外屢試不爽的方法,我們今天就來講一個間諜的故事。
韓非子大家都應該記得吧,法家的代表人物,主張建立法制社會,實施重罰制度,真是非常有遠見呀!看看現在社會在呼吁什么,建立法制化的社會,這在2000多年前就已經提出了。大家可能還不知道,法家還有一個非常重要的代表人物——李斯。李斯是秦國的丞相,最終被殘忍車裂的那位,李斯和韓非子都是荀子的學生,李斯是師兄,韓非子是師弟,若干年后,李斯成為最強諸侯國秦國的上尉,致力于統一全國,于是安插了間諜到各個國家的重要人物的身邊,以獲取必要的信息,韓非子作為韓國的重量級人物,身邊自然有不少間諜,韓非子做的事,李斯都了如指掌,那可是相隔千里!怎么做到的呢?間諜呀!我們先通過程序把這個過程展現一下,看看李斯是怎么監控韓非子的,先看兩個主角的類圖,如圖22-1所示。

圖22-1 監控者和被監控者
僅有這兩個對象還是不夠的,我們要解決的是李斯是怎么監控韓非子的?創建一個后臺線程一直處于運行狀態,一旦發現韓非子在吃飯或者娛樂就觸發事件?這是真實世界的翻版,安排了一個間諜,觀察韓非子的生活起居,并上報給李斯,然后李斯再觸發update事件,類圖繼續擴充,如圖22-2所示。

圖22-2 通過后臺線程監控
這個類圖應該是程序員最容易想到的,你要監控,我就給你找個間諜角色(Spy類),我們來看程序的實現,先看我們的主角韓非子的接口(類似于韓非子這樣的人,被觀察者角色),如代碼清單22-1所示。
代碼清單22-1 被觀察者接口
public?interface?IHanFeiZi?{
?????//韓非子也是人,也要吃早飯的
?????public?void?haveBreakfast();
?????//韓非之也是人,是人就要娛樂活動
?????public?void?haveFun();
}
對接口進行擴充,增加了兩個狀態isHavingBreakfast(是否在吃早飯)和isHavingFun(是否在娛樂),以方便Spy進行監控,如代碼清單22-2所示。
代碼清單22-2 具體的被觀察者
public?class?HanFeiZi?implements?IHanFeiZi{
?????//韓非子是否在吃飯,作為監控的判斷標準
?????private?boolean?isHavingBreakfast?=?false;
?????//韓非子是否在娛樂
?????private?boolean?isHavingFun?=?false;
?????//韓非子要吃飯了
?????public?void?haveBreakfast(){
?????????????System.out.println("韓非子:開始吃飯了...");
?????????????this.isHavingBreakfast?=true;
?????}
?????//韓非子開始娛樂了
?????public?void?haveFun(){
?????????????System.out.println("韓非子:開始娛樂了...");
?????????????this.isHavingFun?=?true;
?????}
?????//以下是bean的基本方法,getter/setter,不多說
?????public?boolean?isHavingBreakfast()?{
?????????????return?isHavingBreakfast;
?????}
?????public?void?setHavingBreakfast(boolean?isHavingBreakfast)?{
?????????????this.isHavingBreakfast?=?isHavingBreakfast;
?????}
?????public?boolean?isHavingFun()?{
?????????????return?isHavingFun;
?????}
?????public?void?setHavingFun(boolean?isHavingFun)?{
?????????????this.isHavingFun?=?isHavingFun;
?????}
}
其中有兩個getter/setter方法,這個就沒有在類圖中表示出來,比較簡單,通過isHavingBreakfast和isHavingFun這兩個布爾型變量來判斷韓非子是否在吃飯或者娛樂,韓非子屬于被觀察者,那還有觀察者李斯,我們來看李斯的接口,如代碼清單22-3所示。
代碼清單22-3 抽象觀察者
public?interface?ILiSi?{
?????//一發現別人有動靜,自己也要行動起來
?????public?void?update(String?context);
}
李斯這類人比較簡單,一發現自己觀察的對象發生了變化,比如吃飯、娛樂,自己立刻也要行動起來,怎么行動呢?如代碼清單22-4所示。
代碼清單22-4 韓非子
public?class?LiSi?implements?ILiSi{
?????//首先李斯是個觀察者,一旦韓非子有活動,他就知道,他就要向老板匯報
?????public?void?update(String?str){
?????????????System.out.println("李斯:觀察到韓非子活動,開始向老板匯報了...");
?????????????this.reportToQinShiHuang(str);
?????????????System.out.println("李斯:匯報完畢...\n");
?????}
?????//匯報給秦始皇
?????private?void?reportToQinShiHuang(String?reportContext){
?????????????System.out.println("李斯:報告,秦老板!韓非子有活動了--->"+reportContext);
?????}
}
兩個重量級的人物都定義出來了,間諜這個“卑鄙”小人是不是也要登臺了,如代碼清單22-5所示。
代碼清單22-5 間諜
class?Spy?extends?Thread{
?????private?HanFeiZi?hanFeiZi;
?????private?LiSi?liSi;
?????private?String?type;
?????//通過構造函數傳遞參數,我要監控的是誰,誰來監控,要監控什么
?????public?Spy(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.isHavingBreakfast()){
???????????????????????????????this.liSi.update("韓非子在吃飯");
???????????????????????????????//重置狀態,繼續監控
???????????????????????????????this.hanFeiZi.setHavingBreakfast(false);
??????????????????????????}???
?????????????????????}else{//監控是否在娛樂
??????????????????????????if(this.hanFeiZi.isHavingFun()){
???????????????????????????????this.liSi.update("韓非子在娛樂");
???????????????????????????????this.hanFeiZi.setHavingFun(false);
??????????????????????????}
?????????????????????}
?????????????}
?????}
}
監控程序繼承了java.lang.Thread類,可以同時啟動多個線程進行監控,Java的多線程機制還是比較簡單的,繼承Thread類,重寫run()方法,然后new SubThread(),再然后subThread.start()就可以啟動一個線程了。我們建立一個場景類來回顧一下這段歷史,如代碼清單22-6所示。
代碼清單22-6 場景類
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)來做監聽,要是用到項目中,你要多少硬件投入進來?你還讓不讓別人的程序運行了?!一臺服務器就跑你這一個程序就完事!
錯誤也看到了,我們必須要修改,這個沒法應用到項目中,而且這個程序根本就不是面向對象的程序,這完全是面向過程的,不改不行,怎么修改呢?我們來想,既然韓非子一吃飯李斯就知道了,那我們為什么不把李斯這個類聚集到韓非子那個類上呢?說改就改,立馬動手,我們來看修改后的類圖,如圖22-3所示。
類圖非常簡單,就是在HanFeiZi類中引用了LiSi實例,看我們程序代碼怎么修改,IHanFeiZi接口完全沒有修改,可以參考代碼清單22-1所示。我們來看實現類的修改,如代碼清單22-7所示。
代碼清單22-7 通過聚集方式的被觀察者
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("韓非子在娛樂");
?????}
}

圖22-3 通過聚集方式監控
韓非子HanFeiZi實現類就把接口的兩個方法實現就可以了,在每個方法中調用LiSi.update()方法,完成李斯觀察韓非子的職責,李斯的接口和實現類都沒有任何改變,請參考代碼清單22-3、22-4。我們再來看看Client程序的變更,如代碼清單22-8所示。
代碼清單22-8 通過聚集方式的場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義出韓非子
?????????????HanFeiZi?hanFeiZi?=?new?HanFeiZi();
?????????????//然后我們看看韓非子在干什么
?????????????hanFeiZi.haveBreakfast();
?????????????//韓非子娛樂了
?????????????hanFeiZi.haveFun();
?????}
}
李斯就不用在場景類中定義了,非常簡單,運行結果相同,不再贅述。
我們思考一下,修改后的程序運行結果正確,效率也比較高,是不是應該樂呵樂呵了?大功告成了?稍等等,你想在戰國爭雄的時候,韓非子這么有名望、有實力的人,就只有秦國關心他嗎?想想也不可能呀,確實有一大幫的各國類似于李斯這樣的人在看著他,監視著他的一舉一動,但是看看我們的程序,你在HanFeiZi這個類中定義:
private ILiSi liSi =new LiSi();
這樣一來只有李斯才能觀察到韓非子,這是不對的,也就是說韓非子的活動只通知了李斯一個人,這不可能;再者說了,李斯只觀察韓非子的吃飯、娛樂嗎?政治傾向不關心嗎?思維傾向不關心嗎?殺人放火不關心嗎?也就說韓非子的一系列活動都要通知李斯,這可怎么辦?要按照上面的例子,我們如何修改?這和開閉原則嚴重違背呀,我們的程序有問題,修改如圖22-4所示。

圖22-4 改進后的觀察者和被觀察者
我們把原有類圖做了兩個修改:
● 增加Observable
實現該接口的都是被觀察者,那韓非子是被觀察者,他當然也要實現該接口了,同時他還有與其他庸人相異的事要做,因此他還是要實現IHanFeizi接口。
● 修改ILiSI接口名稱為Observer
接口名稱修改了一下,這樣顯得更抽象化,所有實現該接口的都是觀察者(類似李斯這樣的)。
Observable是被觀察者,就是類似韓非子這樣的人,在Observable接口中有三個比較重要的方法,分別是addObserver增加觀察者,deleteObserver刪除觀察者,notifyObservers通知所有的觀察者,這是什么意思呢?我這里有一個信息,一個對象,我可以允許有多個對象來察看,你觀察也成,我觀察也成,只要是觀察者就成,也就是說我的改變或動作執行,會通知其他的對象,看程序會更明白一點,先看Observable接口,如代碼清單22-9所示。
代碼清單22-9 被觀察者接口
public?interface?Observable?{
?????//增加一個觀察者
?????public?void?addObserver(Observer?observer);
?????//刪除一個觀察者
?????public?void?deleteObserver(Observer?observer);
?????//既然要觀察,我發生改變了他也應該有所動作,通知觀察者
?????public?void?notifyObservers(String?context);
}
這是一個通用的被觀察者接口,所有的被觀察者都可以實現這個接口。再來看韓非子的實現類,如代碼清單22-10所示。
代碼清單22-10 被觀察者實現類
public?class?HanFeiZi?implements?Observable?,IHanFeiZi{
?????//定義個變長數組,存放所有的觀察者
?????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("韓非子在娛樂");
?????}
}
觀察者只是把原有的ILiSi接口修改了一個名字而已,如代碼清單22-11所示。
代碼清單22-11 觀察者接口
public?interface?Observer?{
?????//一發現別人有動靜,自己也要行動起來
?????public?void?update(String?context);
}
然后是三個很無恥的觀察者,咱先看看真實的李斯,如代碼清單22-12所示。
代碼清單22-12 具體的觀察者
public?class?LiSi?implements?Observer{
?????//首先李斯是個觀察者,一旦韓非子有活動,他就知道,他就要向老板匯報
?????public?void?update(String?str){
?????????????System.out.println("李斯:觀察到韓非子活動,開始向老板匯報了...");
?????????????this.reportToQinShiHuang(str);
?????????????System.out.println("李斯:匯報完畢...\n");
?????}
?????//匯報給秦始皇
?????private?void?reportToQinShiHuang(String?reportContext){
?????????????System.out.println("李斯:報告,秦老板!韓非子有活動了-->"+reportContext);
?????}
}
李斯是真有其人,以下兩個觀察者王斯和劉斯是杜撰出來的,如代碼清單22-13所示。
代碼清單22-13 杜撰的觀察者
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+",--所以我悲傷呀!");
?????}
}
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+",--所以我快樂呀!"?);
?????}
}
所有的歷史人物都在場了,那我們來看看這場歷史鬧劇是如何演繹的,如代碼清單22-14所示。
代碼清單22-14 場景類
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程序,因為你的業務都發生了變化。這就是觀察者模式。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖