33.2 門面模式VS中介者模式
門面模式為復雜的子系統提供一個統一的訪問界面,它定義的是一個高層接口,該接口使得子系統更加容易使用,避免外部模塊深入到子系統內部而產生與子系統內部細節耦合的問題。中介者模式使用一個中介對象來封裝一系列同事對象的交互行為,它使各對象之間不再顯式地引用,從而使其耦合松散,建立一個可擴展的應用架構。
33.2.1 中介者模式實現工資計算
大家工作會得到工資,那么工資與哪些因素有關呢?這里假設工資與職位、稅收有關,職位提升工資就會增加,同時稅收也增加,職位下降了工資也同步降低,當然稅收也降低。而如果稅收比率增加了呢?工資自然就減少了!這三者之間兩兩都有關系,很適合中介者模式的場景,類圖如圖33-4所示。

圖33-4 工資、職位、稅收的示意類圖
類圖中的方法比較簡單,我們主要分析的是三者之間的關系,通過類圖可以發現三者之間已經沒有耦合,原本在需求分析時我們發現三者有直接的交互,采用中介者模式后,三個對象之間已經相互獨立了,全部委托中介者完成。我們在類圖中還定義了一個抽象同事類,它是一個標志性接口,其子類都是同事類,都可以被中介者接收,如代碼清單33-11所示。
代碼清單33-11 抽象同事類
public?abstract?class?AbsColleague?{
?????//每個同事類都對中介者非常了解
?????protected?AbsMediator?mediator;
?????public?AbsColleague(AbsMediator?_mediator){
?????????????this.mediator?=?_mediator;
?????}
}
在抽象同事類中定義了每個同事類對中介者都非常了解,如此才能把請求委托給中介者完成。三個同事類都具有相同的設計,即定義一個業務接口以及每個對象必須實現的職責,同時既然是同事類就都繼承AbsColleague。抽象同事類只是一個標志性父類,并沒有限制子類的業務邏輯,因此每一個同事類并沒有違背單一職責原則。首先來看職位接口,如代碼清單33-12所示。
代碼清單33-12 職位接口
public?interface?IPosition?{
?????//升職
?????public?void?promote();
?????//降職
?????public?void?demote();
}
職位會有升有降,職位變化如代碼清單33-13所示。
代碼清單33-13 職位
public?class?Position?extends?AbsColleague?implements?IPosition?{
?????public?Position(AbsMediator?_mediator){
?????????????super(_mediator);
?????}
?????public?void?demote()?{
?????????????super.mediator.down(this);
?????}
?????public?void?promote()?{??????????
?????????????super.mediator.up(this);
?????}
}
每一個職位的升降動作都委托給中介者執行,具體一個職位升降影響到誰這里沒有定義,完全由中介者完成,簡單而且擴展性非常好。下面我們來看工資接口,如代碼清單33-14所示。
代碼清單33-14 工資接口
public?interface?ISalary?{
?????//加薪
?????public?void?increaseSalary();
?????//降薪
?????public?void?decreaseSalary();
}
工資也會有升有降,如代碼清單33-15所示。
代碼清單33-15 工資
public?class?Salary?extends?AbsColleague?implements?ISalary?{
?????public?Salary(AbsMediator?_mediator){
?????????????super(_mediator);
?????}
?????public?void?decreaseSalary()?{
?????????????super.mediator.down(this);
?????}
?????public?void?increaseSalary()?{
?????????????super.mediator.up(this);
?????}
}
交稅是公民的義務,稅收接口如代碼清單33-16所示。
代碼清單33-16 稅收接口
public?interface?ITax?{
?????//稅收上升
?????public?void?raise();
?????//稅收下降
?????public?void?drop();
}?
稅收的變化對我們的工資當然有影響,如代碼清單33-17所示。
代碼清單33-17 稅收
public?class?Tax?extends?AbsColleague?implements?ITax?{
?????//注入中介者
?????public?Tax(AbsMediator?_mediator){
?????????????super(_mediator);
?????}
?????public?void?drop()?{
?????????????super.mediator.down(this);
?????}
?????public?void?raise()?{
?????????????super.mediator.up(this);
?????}
}
以上同事類的業務都委托給了中介者,其本類已經沒有任何的邏輯了,非常簡單,現在的問題是中介者類非常復雜,因為它要處理三者之間的關系。我們首先來看抽象中介者,如代碼清單33-18所示。
代碼清單33-18 抽象中介者
public?abstract?class?AbsMediator?{
?????//工資
?????protected?final?ISalary?salary;
?????//職位
?????protected?final?IPosition?position;
?????//稅收
?????protected?final?ITax?tax;
?????public?AbsMediator(){
?????????????salary?=?new?Salary(this);
?????????????position?=?new?Position(this);
?????????????tax?=?new?Tax(this);
?????}
?????//工資增加了
?????public?abstract?void?up(ISalary?_salary);
?????//職位提升了
?????public?abstract?void?up(IPosition?_position);
?????//稅收增加了
?????public?abstract?void?up(ITax?_tax);
?????//工資降低了
?????public?abstract?void?down(ISalary?_salary);
?????//職位降低了
?????public?abstract?void?down(IPosition?_position);
?????//稅收降低了
?????public?abstract?void?down(ITax?_tax);?????
}
在抽象中介者中我們定義了6個方法,分別處理職位升降、工資升降以及稅收升降的業務邏輯,采用Java多態機制來實現,我們來看實現類,如代碼清單33-19所示。
代碼清單33-19 中介者
public?class?Mediator?extends?AbsMediator{
?????//工資增加了
?????public?void?up(ISalary?_salary)?{
?????????????upSalary();
?????????????upTax();
?????}
?????//職位提升了
?????public?void?up(IPosition?position)?{
?????????????upPosition();
?????????????upSalary();
?????????????upTax();
?????}
?????//稅收增加了
?????public?void?up(ITax?tax)?{
?????????????upTax();
?????????????downSalary();
?????}
?????/*
?????*工資、職位、稅收降低的處理方法相同,不再贅述
?????*/
?????//工資增加
?????private?void?upSalary(){
?????????????System.out.println("工資翻倍,樂翻天");
?????}
?????private?void?upTax(){
?????????????System.out.println("稅收上升,為國家做貢獻");
?????}
?????private?void?upPosition(){
?????????????System.out.println("職位上升一級,狂喜");
?????}
?????private?void?downSalary(){
?????????????System.out.println("經濟不景氣,降低工資");
?????}
?????private?void?downTax(){
?????????????System.out.println("稅收減低,國家收入減少");??????????
?????}
?????private?void?downPostion(){
?????????????System.out.println("官降三級,比自殺還痛苦");
?????}
}
該類的方法較多,但是還是非常簡單的,它的12個方法分為兩大類型:一類是每個業務的獨立流程,比如增加工資,僅僅實現單獨增加工資的職能,而不關心職位、稅收是如何變化的,該類型的方法是private私有類型,只能提供本類內訪問;另一類是實現抽象中介者定義的方法,完成具體的每一個邏輯,比如職位上升,同時也引起了工資增加、稅收增加。我們編寫一個場景類,看看運行結果,如代碼清單33-20所示。
代碼清單33-20 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義中介者
?????????????Mediator?mediator?=?new?Mediator();
?????????????//定義各個同事類
?????????????IPosition?position?=?new?Position(mediator);
?????????????ISalary?salary?=?new?Salary(mediator);
?????????????ITax?tax?=?new?Tax(mediator);
?????????????//職位提升了
?????????????System.out.println("===職位提升===");
?????????????position.promote();
?????}
}
運行結果如下所示:
===職位提升===
職位上升一級,狂喜
工資翻倍,樂翻天
稅收上升,為國家做貢獻
我們回過頭來分析一下設計,在接收到需求后我們發現職位、工資、稅收之間有著緊密的耦合關系,如果不采用中介者模式,則每個對象都要與其他兩個對象進行通信,這勢必會增加系統的復雜性,同時也使系統處于僵化狀態,很難實現擁抱變化的理想。通過增加一個中介者,每個同事類的職位、工資、稅收都只與中介者通信,中介者封裝了各個同事類之間的邏輯關系,方便系統的擴展和維護。
33.2.2 門面模式實現工資計算
工資計算是一件非常復雜的事情,簡單來說,它是對基本工資、月獎金、崗位津貼、績效、考勤、稅收、福利等因素綜合運算后的一個數字。即使設計一個HR(人力資源)系統,員工工資計算也是非常復雜的模塊,但是對于外界,比如高管層,最希望看到的結果是張三拿了多少錢,李四拿了多少錢,而不是看中間的計算過程,怎么計算那是人事部門的事情。換句話說,對外界的訪問者來說,它只要傳遞進去一個人員名稱和月份即可獲得工資數,而不用關心其中的計算有多么復雜,這就用得上門面模式了。
門面模式對子系統起封裝作用,它可以提供一個統一的對外服務接口,如圖33-5所示。

圖33-5 HR系統的類圖
該類圖主要實現了工資計算,通過HRFacade門面可以查詢用戶的工資以及出勤天數等,而不用關心這個工資或者出勤天數是怎么計算出來的,從而屏蔽了外系統對工資計算模塊的內部細節依賴。我們先看子系統內部的各個實現,考勤情況如代碼清單33-21所示。
代碼清單33-21 考勤情況
public?class?Attendance?{
?????//得到出勤天數
?????public?int?getWorkDays(){
?????????????return?(new?Random()).nextInt(30);
?????}
}
非常簡單,只用一個方法獲得一個員工的出勤天數。我們再來看獎金計算,如代碼清單33-22所示。
代碼清單33-22 獎金計算
public?class?Bonus?{
?????//考勤情況
?????private?Attendance?atte?=?new?Attendance();
?????//獎金
?????public?int?getBonus(){
?????????????//獲得出勤情況
?????????????int?workDays?=?atte.getWorkDays();
?????????????//獎金計算模型
?????????????int?bonus?=?workDays?*?1800?/?30;
?????????????return?bonus;
?????}
}
我們在這里實現了一個示意方法,實際的獎金計算是非常復雜的,與考勤、績效、基本工資、崗位都有關系,單單一個獎金計算就可以設計出一個門面。我們再來看基本工資,這個基本上是按照職位而定的,比較固定,如代碼清單33-23所示。
代碼清單33-23 基本工資
public?class?BasicSalary?{
?????//獲得一個人的基本工資
?????public?int?getBasicSalary(){
?????????????return?2000;
?????}
}
我們定義了員工的基本工資都為2000元,沒有任何浮動的余地。再來看績效,如代碼清單33-24所示。
代碼清單33-24 績效
public?class?Performance?{
?????//基本工資
?????private?BasicSalary?salary?=?new?BasicSalary();
?????//績效獎勵
?????public?int?getPerformanceValue(){
?????????????//隨機績效
?????????????int?perf?=?(new?Random()).nextInt(100);
?????????????return?salary.getBasicSalary()?*?perf?/100;
?????}
}
績效按照一個非常簡單的算法,即基本工資乘以一個隨機的百分比。我們再來看稅收,如代碼清單33-25所示。
代碼清單33-25 稅收
public?class?Tax?{
?????//收取多少稅金
?????public?int?getTax(){
?????????????//交納一個隨機數量的稅金
?????????????return?(new?Random()).nextInt(300);
?????}
}
一個計算員工薪酬的所有子元素都已經具備了,剩下的就是編寫組合邏輯類,總工資的計算如代碼清單33-26所示。
代碼清單33-26 總工資計算
public?class?SalaryProvider?{
?????//基本工資
?????private?BasicSalary?basicSalary?=?new?BasicSalary();
?????//獎金
?????private??Bonus?bonus?=?new?Bonus();
?????//績效
?????private??Performance?perf?=?new?Performance();
?????//稅收
?????private??Tax?tax?=?new?Tax();
?????//獲得用戶的總收入
?????public?int?totalSalary(){
?????????????return?basicSalary.getBasicSalary()?+?bonus.getBonus()?+?perf.getPerformanceValue()?-?tax.getTax();
?????}
}
這里只是對前面的元素值做了一個加減法計算,這是對實際HR系統的簡化處理,如果把這個類暴露給外系統,那么被修改的風險是非常大的,因為它的方法totalSalary是一個具體的業務邏輯。我們采用門面模式的目的是要求門面是無邏輯的,與業務無關,只是一個子系統的訪問入口。門面模式只是一個技術層次上的實現,全部業務還是在子系統內實現。我們來看HR門面,如代碼清單33-27所示。
代碼清單33-27 HR門面
public?class?HRFacade?{
?????//總工資情況
?????private??SalaryProvider?salaryProvider?=?new?SalaryProvider();
?????//考勤情況
?????private??Attendance?attendance?=?new?Attendance();
?????//查詢一個人的總收入
?????public?int?querySalary(String?name,Date?date){
?????????????return?salaryProvider.totalSalary();??????????
?????}
?????//查詢一個員工一個月工作了多少天
?????public?int?queryWorkDays(String?name){
?????????????return?attendance.getWorkDays();
?????}
}
所有的行為都是委托行為,由具體的子系統實現,門面只是提供了一個統一訪問的基礎而已,不做任何的校驗、判斷、異常等處理。我們編寫一個場景類查看運行結果,如代碼清單33-28所示。
代碼清單33-28 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義門面
?????????????HRFacade?facade?=?new?HRFacade();
?????????????System.out.println("===外系統查詢總收入===");
?????????????int?salary?=?facade.querySalary("張三",new?Date(System.
?????????????????currentTimeMillis()));
?????????????System.out.println(?"張三?11月?總收入為:"?+salary);
?????????????//再查詢出勤天數
?????????????System.out.println("\n===外系統查詢出勤天數===");
?????????????int?workDays?=?facade.queryWorkDays("李四");
?????????????System.out.println("李四?本月出勤:"?+workDays);
?????}
}
運行結果如下所示:
===外系統查詢總收入===
張三 11月 總收入為:4133
===外系統查詢出勤天數===
李四 本月出勤:22
在該例中,我們使用了門面模式對薪水計算子系統進行封裝,避免子系統內部復雜邏輯外泄,確保子系統的業務邏輯的單純性,即使業務流程需要變更,影響的也是子系統內部功能,比如獎金需要與基本工資掛鉤,這樣的修改對外系統來說是透明的,只需要子系統內部變更即可。
33.2.3 最佳實踐
門面模式和中介者模式之間的區別還是比較明顯的,門面模式是以封裝和隔離為主要任務,而中介者模式則是以調和同事類之間的關系為主,因為要調和,所以具有了部分的業務邏輯控制。兩者的主要區別如下:
● 功能區別
門面模式只是增加了一個門面,它對子系統來說沒有增加任何的功能,子系統若脫離門面模式是完全可以獨立運行的。而中介者模式則增加了業務功能,它把各個同事類中的原有耦合關系移植到了中介者,同事類不可能脫離中介者而獨立存在,除非是想增加系統的復雜性和降低擴展性。
● 知曉狀態不同
對門面模式來說,子系統不知道有門面存在,而對中介者來說,每個同事類都知道中介者存在,因為要依靠中介者調和同事之間的關系,它們對中介者非常了解。
● 封裝程度不同
門面模式是一種簡單的封裝,所有的請求處理都委托給子系統完成,而中介者模式則需要有一個中心,由中心協調同事類完成,并且中心本身也完成部分業務,它屬于更進一步的業務功能封裝。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——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種設計模式彩圖