25.4 訪問者模式的擴展
訪問者模式是經常用到的模式,雖然你不注意,有可能你起的名字也不是什么Visitor,但是它確實是非常容易使用到的,在這里我提出兩個擴展的功能供大家參考。
25.4.1 統計功能
在例子中我們也提到訪問者的統計功能,匯總和報表是金融類企業非常常用的功能,基本上都是一堆的計算公式,然后出一個報表,很多項目采用了數據庫的存儲過程來實現,我不是很推薦這種方式,除非海量數據處理,一個晚上要批處理上億、幾十億條的數據,除了存儲過程來處理還沒有其他辦法,你要是用應用服務器來處理,連接數據庫的網絡就是處于100%占用狀態,一個晚上也未必能處理完這批數據!除了這種海量數據外,我建議數據統計和報表的批處理通過訪問者模式來處理會比較簡單。好,那我們來統計一下公司人員的工資總額,先看類圖,如圖25-6所示。

圖25-6 統計功能的訪問者模式
沒什么變化?仔細看IVisitor接口,增加了一個getTotalSalary方法,在Visitor實現類中實現該方法。我們先看接口,如代碼清單25-17所示。
代碼清單25-17 抽象訪問者
public?interface?IVisitor?{
?????//首先定義我可以訪問普通員工
?????public?void?visit(CommonEmployee?commonEmployee);
?????//其次定義,我還可以訪問部門經理
?????public?void?visit(Manager?manager);
?????//統計所有員工工資總和
?????public?int?getTotalSalary();
}
這就多了一個getTotalSalary方法。我們再來看實現類,如代碼清單25-18所示。
代碼清單25-18 具體訪問者
public?class?Visitor?implements?IVisitor?{
?????//部門經理的工資系數是5
?????private?final?static?int?MANAGER_COEFFICIENT?=?5;
?????//員工的工資系數是2
?????private?final?static?int?COMMONEMPLOYEE_COEFFICIENT?=?2;
?????//普通員工的工資總和
?????private?int?commonTotalSalary?=?0;
?????//部門經理的工資總和
?????private?int?managerTotalSalary?=0;
?????//計算部門經理的工資總和
?????private?void?calManagerSalary(int?salary){
?????????????this.managerTotalSalary?=?this.managerTotalSalary?+?salary
?????????????*MANAGER_COEFFICIENT?;
?????}
?????//計算普通員工的工資總和
?????private?void?calCommonSlary(int?salary){
?????????????this.commonTotalSalary?=?this.commonTotalSalary?+?
?????????????salary*COMMONEMPLOYEE_COEFFICIENT;
?????}
?????//獲得所有員工的工資總和
?????public?int?getTotalSalary(){
?????????????return?this.commonTotalSalary?+?this.managerTotalSalary;
?????}
}
員工和經理層的信息就不再展示了,請參考代碼清單25-6。程序還是比較簡單的,分別計算普通員工和經理級員工的工資總和,然后加起來。注意,我們在實現時已經考慮員工工資和經理工資的系數不同。
我們再來看Client類的模擬,如代碼清單25-19所示。
代碼清單25-19 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????IVisitor?visitor?=?new?Visitor();
?????????????for(Employee?emp:mockEmployee()){
?????????????????????emp.accept(visitor);
?????????????}
?????????????System.out.println("本公司的月工資總額是:"+visitor.getTotalSalary());
?????}
}
其中mockEmployee靜態方法沒有任何改動,請參考代碼清單25-10,在此不再贅述。運行結果如下所示:
?
| 姓名:張三 | 性別:男 | 薪水:1800 | 工作:編寫Java程序,絕對的藍領、苦工加搬運工 |
|-----|-----|-----|-----|
| 姓名:李四 | 性別:女 | 薪水:1900 | 工作:頁面美工,審美素質太不流行了! |
| 姓名:王五 | 性別:男 | 薪水:18750 | 業績:基本上是負值,但是我會拍馬屁呀 |
本公司的月工資總額是:101150
然后你想修改工資的系數,沒有問題!想換個展示格式,也沒有問題!多多練習吧,這都是非常簡單的。
25.4.2 多個訪問者
在實際的項目中,一個對象,多個訪問者的情況非常多。其實我們上面例子就應該是兩個訪問者,為什么呢?報表分兩種:第一種是展示表,通過數據庫查詢,把結果展示出來,這個就類似于我們的那個列表;第二種是匯總表,這個是需要通過模型或者公式計算出來的,一般都是批處理結果,這個類似于我們計算工資總額,這兩種報表格式是對同一堆數據的兩種處理方式。從程序上看,一個類就有個不同的訪問者了。修改一下類圖,如圖25-7所示。
類圖看著挺復雜,其實也沒什么復雜的,只是多了兩個接口和兩個實現類,分別負責展示表和匯總表的業務處理,IVisitor接口沒有改變,請參考代碼清單25-5所示代碼,這里不再贅述。我們來看展示報表接口,如代碼清單25-20所示。
代碼清單25-20 展示表接口
public?interface?IShowVisitor?extends?IVisitor?{
?????//展示報表
?????public?void?report();
}
展示表的實現也比較簡單,如代碼清單25-21所示。
代碼清單25-21 具體展示表
public?class?ShowVisitor?implements?IShowVisitor?{
?????private?String?info?=?"";
?????//打印出報表
?????public?void?report()?{
??????????System.out.println(this.info);
?????}
?????//訪問普通員工,組裝信息
?????public?void?visit(CommonEmployee?commonEmployee)?{
??????????this.info?=?this.info?+?this.getBasicInfo(commonEmployee)
??????????+?"工作:"+commonEmployee.getJob()+"\t\n";
?????}
?????//訪問經理,然后組裝信息
?????public?void?visit(Manager?manager)?{
??????????this.info?=?this.info?+?this.getBasicInfo(manager)?+??"業績:
????????????"+manager.getPerformance()?+?"\t\n";
?????}
?????//組裝出基本信息
?????private?String?getBasicInfo(Employee?employee){
??????????String?info?=?"姓名:"?+?employee.getName()?+?"\t";
??????????info?=?info?+?"性別:"?+?(employee.getSex()?==?Employee.FEMALE?"女":
??????????"男")?+?"\t";
??????????info?=?info?+?"薪水:"?+?employee.getSalary()??+?"\t";
??????????return?info;
?????}
}

圖25-7 多訪問者的類圖
匯總表實現數據匯總功能,其接口如代碼清單25-22所示。
代碼清單25-22 匯總表接口
public?interface?ITotalVisitor?extends?IVisitor?{
?????//統計所有員工工資總和
?????public?void?totalSalary();
}
就一句話,非常簡單,我們再來看具體的匯總表訪問者,如代碼清單25-23所示。
代碼清單25-23 具體匯總表
public?class?TotalVisitor?implements?ITotalVisitor?{
?????//部門經理的工資系數是5
?????private?final?static?int?MANAGER_COEFFICIENT?=?5;
?????//員工的工資系數是2
?????private?final?static?int?COMMONEMPLOYEE_COEFFICIENT?=?2;
?????//普通員工的工資總和
?????private?int?commonTotalSalary?=?0;
?????//部門經理的工資總和
?????private?int?managerTotalSalary?=0;
?????public?void?totalSalary()?{
??????????System.out.println("本公司的月工資總額是"?+?(this.commonTotalSalary?+
??????????this.managerTotalSalary));
?????}
?????//訪問普通員工,計算工資總額
?????public?void?visit(CommonEmployee?commonEmployee)?{
??????????this.commonTotalSalary?=?this.commonTotalSalary?+?commonEmployee.getSalary()?*COMMONEMPLOYEE_COEFFICIENT;
?????}
?????//訪問部門經理,計算工資總額
?????public?void?visit(Manager?manager)?{
??????????this.managerTotalSalary?=?this.managerTotalSalary?+?manager.getSalary()?*MANAGER_COEFFICIENT?;
?????}
}
最后看我們的場景類如何計算出工資總額,如代碼清單25-24所示。
代碼清單25-24 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//展示報表訪問者
?????????????IShowVisitor?showVisitor?=?new?ShowVisitor();
?????????????//匯總報表的訪問者
?????????????ITotalVisitor?totalVisitor?=?new?TotalVisitor();
?????????????for(Employee?emp:mockEmployee()){
?????????????????????emp.accept(showVisitor);??//接受展示報表訪問者
?????????????????????emp.accept(totalVisitor);//接受匯總表訪問者
?????????????}
?????????????//展示報表
?????????????showVisitor.report();??????????
?????????????//匯總報表
?????????????totalVisitor.totalSalary();
?????}
}
運行結果如下所示:
?
| 姓名:張三 | 性別:男 | 薪水:1800 | 工作:編寫Java程序,絕對的藍領、苦工加搬運工 |
|-----|-----|-----|-----|
| 姓名:李四 | 性別:女 | 薪水:1900 | 工作:頁面美工,審美素質太不流行了! |
| 姓名:王五 | 性別:男 | 薪水:18750 | 業績:基本上是負值,但是我會拍馬屁啊 |
本公司的月工資總額是101150
大家可以再深入地想象,一堆數據從幾個角度來分析,那是什么?即數據挖掘(Data Mining),數據的上切、下鉆等處理,大家有興趣看可以翻看數據挖掘或者商業智能(BI)的書。
25.4.3 雙分派
說到訪問者模式就不得不提一下雙分派(double dispatch)問題,什么是雙分派呢?我們先來解釋一下什么是單分派(single dispatch)和多分派(multiple dispatch),單分派語言處理一個操作是根據請求者的名稱和接收到的參數決定的,在Java中有靜態綁定和動態綁定之說,它的實現是依據重載(overload)和覆寫(override)實現的,我們來說一個簡單的例子。
例如,演員演電影角色,一個演員可以扮演多個角色,我們先定義一個影視中的兩個角色:功夫主角和白癡配角,如代碼清單25-25所示。
代碼清單25-25 角色接口及實現類
public?interface?Role?{
?????//演員要扮演的角色
}
public?class?KungFuRole?implements?Role?{
?????//武功天下第一的角色
}
public?class?IdiotRole?implements?Role?{
?????//一個弱智角色?????
}
角色有了,我們再定義一個演員抽象類,如代碼清單25-26所示。
代碼清單25-26 抽象演員
public?abstract?class?AbsActor?{
?????//演員都能夠演一個角色
?????public?void?act(Role?role){
?????????????System.out.println("演員可以扮演任何角色");
?????}?????
?????//可以演功夫戲
?????public?void?act(KungFuRole?role){
?????????????System.out.println("演員都可以演功夫角色");
?????}
}
很簡單,這里使用了Java的重載,我們再來看青年演員和老年演員,采用覆寫的方式來細化抽象類的功能,如代碼清單25-27所示。
代碼清單25-27 青年演員和老年演員
public?class?YoungActor?extends?AbsActor?{
?????//年輕演員最喜歡演功夫戲
?????public?void?act(KungFuRole?role){
?????????????System.out.println("最喜歡演功夫角色");
?????}
}
public?class?OldActor?extends?AbsActor?{
?????//不演功夫角色
?????public?void?act(KungFuRole?role){
?????????????System.out.println("年齡大了,不能演功夫角色");
?????}
}
覆寫和重載都已經實現,我們編寫一個場景,如代碼清單25-28所示。
代碼清單25-28 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義一個演員
?????????????AbsActor?actor?=?new?OldActor();
?????????????//定義一個角色
?????????????Role?role?=?new?KungFuRole();
?????????????//開始演戲
?????????????actor.act(role);
?????????????actor.act(new?KungFuRole());
?????}
}
猜猜看運行結果是什么?很簡單,運行結果如下所示。
演員可以扮演任何角色
年齡大了,不能演功夫角色
重載在編譯器期就決定了要調用哪個方法,它是根據role的表面類型而決定調用act(Role role)方法,這是靜態綁定;而Actor的執行方法act則是由其實際類型決定的,這是動態綁定。
一個演員可以扮演很多角色,我們的系統要適應這種變化,也就是根據演員、角色兩個對象類型,完成不同的操作任務,該如何實現呢?很簡單,我們讓訪問者模式上場就可以解決該問題,只要把角色類稍稍修改即可,如代碼清單25-29所示。
代碼清單25-29 引入訪問者模式
public?interface?Role?{
?????//演員要扮演的角色
?????public?void?accept(AbsActor?actor);
}
public?class?KungFuRole?implements?Role?{
?????//武功天下第一的角色
?????public?void?accept(AbsActor?actor){
?????????????actor.act(this);
?????}
}
public?class?IdiotRole?implements?Role?{
?????//一個弱智角色,由誰來扮演
?????public?void?accept(AbsActor?actor){
?????????????actor.act(this);
?????}
}
場景類稍有改動,如代碼清單25-30所示。
代碼清單25-30 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義一個演員
?????????????AbsActor?actor?=?new?OldActor();
?????????????//定義一個角色???
?????????????Role?role?=?new?KungFuRole();
?????????????//開始演戲
?????????????role.accept(actor);
?????}
}
運行結果如下所示。
年齡大了,不能演功夫角色
看到沒?不管演員類和角色類怎么變化,我們都能夠找到期望的方法運行,這就是雙反派。雙分派意味著得到執行的操作決定于請求的種類和兩個接收者的類型,它是多分派的一個特例。從這里也可以看到Java是一個支持雙分派的單分派語言。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——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種設計模式彩圖