第14章 中介者模式
14.1 進銷存管理是這個樣子的嗎
大家都來自五湖四海,都要生存,于是都找了個靠山——公司,就是給你發薪水的地方。公司要想盡辦法贏利賺錢,贏利方法則不盡相同,但是各個公司都有相同的三個環節:采購、銷售和庫存。這個怎么說呢?比如一個軟件公司,要開發軟件,就需要購買開發環境,如Windows操作系統、數據庫產品等,這就是采購;開發完產品還要把產品推銷出去;有產品就必然有庫存,軟件產品也有庫存,雖然不需要占用庫房空間,但也要占用光盤或硬盤,這也是庫存。再比如做咨詢服務的公司,它要采購什么?采購知識,采購經驗,這是這類企業的生存之本,銷售的也是知識和經驗,庫存同樣是知識和經驗。既然進銷存是如此重要,我們今天就來講講它的原理和設計,我相信很多人都已經開發過這種類型的軟件,基本上都形成了固定套路,不管是單機版還是網絡版,一般的做法都是通過數據庫來完成相關產品的管理,相對來說這還是比較簡單的項目,三個模塊的示意圖如圖14-1所示。

圖14-1 進銷存示意圖
我們從這個示意圖上可以看出,三個模塊是相互依賴的。我們就以一個終端銷售商(以服務最終客戶為目標的企業,比如某某超市、某某商店等)為例,采購部門要采購IBM的電腦,它根據以下兩個要素來決定采購數量。
● 銷售情況
銷售部門要反饋銷售情況,暢銷就多采購,滯銷就不采購。
● 庫存情況
即使是暢銷產品,庫存都有1000臺了,每天才賣出去10臺,也就不需要再采購了!
銷售模塊是企業的贏利核心,對其他兩個模塊也有影響:
● 庫存情況
庫房有貨,才能銷售,空手套白狼是不行的。
● 督促采購
在特殊情況下,比如一個企業客戶要一次性購買100臺電腦,庫存只有80臺,這時需要催促采購部門趕快采購!
同樣地,庫存管理也對其他兩個模塊有影響。庫房是有容積限制的,不可能無限大,所以就有了清倉處理,那就要求采購部門停止采購,同時銷售部門進行打折銷售。
從以上分析來看,這三個模塊都有自己的行為,并且與其他模塊之間的行為產生關聯,類似于我們辦公室的同事,大家各干各的活,但是彼此之間還是有交叉的,于是彼此之間就產生緊耦合,也就是一個團隊。我們先來實現這個進銷存,類圖如圖14-2所示。

圖14-2 簡單的進銷存類圖
Purchase負責采購管理,buyIBMComputer指定了采購IBM電腦,refuseBuyIBM是指不再采購IBM了,源代碼如代碼清單14-1所示。
代碼清單14-1 采購管理
public?class?Purchase?{
?????//采購IBM電腦
?????public?void?buyIBMcomputer(int?number){
?????????????//訪問庫存
?????????????Stock?stock?=?new?Stock();
?????????????//訪問銷售
?????????????Sale?sale?=?new?Sale();
?????????????//電腦的銷售情況
?????????????int?saleStatus?=?sale.getSaleStatus();
?????????????if(saleStatus>80){??//銷售情況良好
?????????????????????System.out.println("采購IBM電腦:"+number?+?"臺");
?????????????????????stock.increase(number);
?????????????}else{??//銷售情況不好
?????????????????????int?buyNumber?=?number/2;??//折半采購
?????????????????????System.out.println("采購IBM電腦:"+buyNumber+?"臺");
?????????????}
?????}??
?????//不再采購IBM電腦
?????public?void?refuseBuyIBM(){
?????????????System.out.println("不再采購IBM電腦");
?????}
}
Purchase定義了采購電腦的標準:如果銷售情況比較好,大于80分,你讓我采購多少我就采購多少;銷售情況不好,你讓我采購100臺,我就采購50臺,對折采購。電腦采購完畢,需要放到庫房中,因此要調用庫存的方法,增加庫存電腦數量。我們繼續來看庫房Stock類,如代碼清單14-2所示。
代碼清單14-2 庫存管理
public?class?Stock?{
?????//剛開始有100臺電腦
?????private?static?int?COMPUTER_NUMBER?=100;
?????//庫存增加
?????public?void?increase(int?number){
?????????????COMPUTER_NUMBER?=?COMPUTER_NUMBER?+?number;
?????????????System.out.println("庫存數量為:"+COMPUTER_NUMBER);
?????}??
?????//庫存降低
?????public?void?decrease(int?number){
?????????????COMPUTER_NUMBER?=?COMPUTER_NUMBER?-?number;
?????????????System.out.println("庫存數量為:"+COMPUTER_NUMBER);
?????}
?????//獲得庫存數量
?????public?int?getStockNumber(){
?????????????return?COMPUTER_NUMBER;
?????}
?????//存貨壓力大了,就要通知采購人員不要采購,銷售人員要盡快銷售
?????public?void?clearStock(){
?????????????Purchase?purchase?=?new?Purchase();
?????????????Sale?sale?=?new?Sale();
?????????????System.out.println("清理存貨數量為:"+COMPUTER_NUMBER);
?????????????//要求折價銷售
?????????????sale.offSale();
?????????????//要求采購人員不要采購
?????????????purchase.refuseBuyIBM();
?????}
}
庫房中的貨物數量肯定有增減,同時庫房還有一個容量顯示,達到一定的容量后就要求對一些商品進行折價處理,以騰出更多的空間容納新產品。于是就有了clearStock方法,既然是清倉處理肯定就要折價銷售了。于是在Sale類中就有了offSale方法,我們來看Sale源代碼,如代碼清單14-3所示。
代碼清單14-3 銷售管理
public?class?Sale?{
?????//銷售IBM電腦
?????public?void?sellIBMComputer(int?number){
?????????????//訪問庫存
?????????????Stock?stock?=?new?Stock();
?????????????//訪問采購
?????????????Purchase?purchase?=?new?Purchase();
?????????????if(stock.getStockNumber()<number){??//庫存數量不夠銷售
????????????????????purchase.buyIBMcomputer(number);
?????????????}
System.out.println("銷售IBM電腦"+number+"臺");
?????????????stock.decrease(number);
?????}
?????//反饋銷售情況,0~100之間變化,0代表根本就沒人賣,100代表非常暢銷,出一個賣一個
?????public?int?getSaleStatus(){
?????????????Random?rand?=?new?Random(System.currentTimeMillis());
?????????????int?saleStatus?=?rand.nextInt(100);
?????????????System.out.println("IBM電腦的銷售情況為:"+saleStatus);
?????????????return?saleStatus;
?????}
?????//折價處理
?????public?void?offSale(){
?????????????//庫房有多少賣多少
?????????????Stock?stock?=?new?Stock();
?????????????System.out.println("折價銷售IBM電腦"+stock.getStockNumber()+"臺");
?????}
}
Sale類中的getSaleStatus是獲得銷售情況,這個當然要出現在Sale類中了。記住要把恰當的類放到恰當的類中,銷售情況只有銷售人員才能反饋出來,通過百分制的機制衡量銷售情況。我們再來看場景類是怎么運行的,場景類如代碼清單14-4所示。
代碼清單14-4 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//采購人員采購電腦
?????????????System.out.println("------采購人員采購電腦--------");
?????????????Purchase?purchase?=?new?Purchase();
?????????????purchase.buyIBMcomputer(100);??????????????
?????????????//銷售人員銷售電腦
?????????????System.out.println("\n------銷售人員銷售電腦--------");
?????????????Sale?sale?=?new?Sale();
?????????????sale.sellIBMComputer(1);???????????
?????????????//庫房管理人員管理庫存
?????????????System.out.println("\n------庫房管理人員清庫處理--------");
?????????????Stock?stock?=?new?Stock();
?????????????stock.clearStock();
?????}
}
我們在場景類中模擬了三種人員的活動:采購人員采購電腦,銷售人員銷售電腦,庫管員管理庫存。運行結果如下所示:
------采購人員采購電腦--------
IBM電腦的銷售情況為:95
采購IBM電腦:100臺
庫存數量為:200
------銷售人員銷售電腦--------
銷售IBM電腦1臺
庫存數量為:199
------庫房管理人員清庫處理--------
清理存貨數量為:199
折價銷售IBM電腦199臺
不再采購IBM電腦
運行結果也是我們期望的,三個不同類型的參與者完成了各自的活動。你有沒有發現這三個類是彼此關聯的?每個類都與其他兩個類產生了關聯關系。迪米特法則認為“每個類只和朋友類交流”,這個朋友類并非越多越好,朋友類越多,耦合性越大,要想修改一個就得修改一片,這不是面向對象設計所期望的,況且這還是僅三個模塊的情況,屬于比較簡單的一個小項目。我們把進銷存擴展一下,如圖14-3所示。

圖14-3 擴展后的進銷存示意圖
這是一個蜘蛛網的結構,別說是編寫程序了,就是給人看估計也能讓一大批人昏倒!每個對象都需要和其他幾個對象交流,對象越多,每個對象要交流的成本也就越大了,只是維護這些對象的交流就能讓一大批程序員望而卻步!從這方面來說,我們已經發現設計的缺陷了,作為一個架構師,發現缺陷就要想辦法修改。
大家都學過網絡的基本知識,網絡拓撲有三種類型:總線型、環型、星型。星型網絡拓撲如圖14-4所示。
在星型網絡拓撲中,每個計算機通過交換機和其他計算機進行數據交換,各個計算機之間并沒有直接出現交互的情況。這種結構簡單,而且穩定,只要中間那個交換機不癱瘓,整個網絡就不會發生大的故障。公司和網吧一般都采用星型網絡。我們是不是可以把這種星型結構引入到我們的設計中呢?我們先畫一個示意圖,如圖14-5所示。

圖14-4 星型網絡拓撲

圖14-5 修改后的進銷存示意圖
加入了一個中介者作為三個模塊的交流核心,每個模塊之間不再相互交流,要交流就通過中介者進行。每個模塊只負責自己的業務邏輯,不屬于自己的則丟給中介者來處理,簡化了各模塊之間的耦合關系,類圖如圖14-6所示。

圖14-6 修改后的進銷存類圖
建立了兩個抽象類AbstractMediator和AbstractColeague,每個對象只是與中介者Mediator之間產生依賴,與其他對象之間沒有直接關系,AbstractMediator的作用是實現中介者的抽象定義,定義了一個抽象方法execute,如代碼清單14-5所示。
代碼清單14-5 抽象中介者
public?abstract?class?AbstractMediator?{
?????protected?Purchase?purchase;
?????protected?Sale?sale;
?????protected?Stock?stock;
?????//構造函數
?????public?AbstractMediator(){
?????????????purchase?=?new?Purchase(this);
?????????????sale?=?new?Sale(this);
?????????????stock?=?new?Stock(this);
?????}??
?????//中介者最重要的方法叫做事件方法,處理多個對象之間的關系
?????public?abstract?void?execute(String?str,Object...objects);
}
再來看具體的中介者,我們可以根據業務的要求產生多個中介者,并劃分各中介者的職責。具體中介者如代碼清單14-6所示。
代碼清單14-6 具體中介者
public?class?Mediator?extends?AbstractMediator?{
?????//中介者最重要的方法
?????public?void?execute(String?str,Object...objects){
?????????????if(str.equals("purchase.buy")){?//采購電腦
????????????????????this.buyComputer((Integer)objects[0]);
?????????????}else?if(str.equals("sale.sell")){?//銷售電腦
????????????????????this.sellComputer((Integer)objects[0]);
?????????????}else?if(str.equals("sale.offsell")){?//折價銷售
????????????????????this.offSell();
?????????????}else?if(str.equals("stock.clear")){?//清倉處理
????????????????????this.clearStock();
?????????????}
?????}
?????//采購電腦
?????private?void?buyComputer(int?number){
?????????????int?saleStatus?=?super.sale.getSaleStatus();
?????????????if(saleStatus>80){??//銷售情況良好
????????????????????System.out.println("采購IBM電腦:"+number?+?"臺");
????????????????????super.stock.increase(number);
?????????????}else{??//銷售情況不好
????????????????????int?buyNumber?=?number/2;??//折半采購
????????????????????System.out.println("采購IBM電腦:"+buyNumber+?"臺");
?????????????}
?????}
?????//銷售電腦
?????private?void?sellComputer(int?number){
?????????????if(super.stock.getStockNumber()<number){??//庫存數量不夠銷售
????????????????????super.purchase.buyIBMcomputer(number);
?????????????}
?????????????super.stock.decrease(number);
?????}
?????//折價銷售電腦
?????private?void?offSell(){
?????????????System.out.println("折價銷售IBM電腦"+stock.getStockNumber()+"臺");
?????}
?????//清倉處理
?????private?void?clearStock(){
?????????????//要求清倉銷售
?????????????super.sale.offSale();
?????????????//要求采購人員不要采購
?????????????super.purchase.refuseBuyIBM();
?????}
}
中介者Mediator定義了多個private方法,其目的是處理各個對象之間的依賴關系,就是說把原有一個對象要依賴多個對象的情況移到中介者的private方法中實現。在實際項目中,一般的做法是中介者按照職責進行劃分,每個中介者處理一個或多個類似的關聯請求。
由于要使用中介者,我們增加了一個抽象同事類,三個具體的實現類分別繼承該抽象類,如代碼清單14-7所示。
代碼清單14-7 抽象同事類
public?abstract?class?AbstractColleague?{
?????protected?AbstractMediator?mediator;
?????public?AbstractColleague(AbstractMediator?_mediator){
?????????????this.mediator?=?_mediator;
?????}
}
采購Purchase類如代碼清單14-8所示。
代碼清單14-8 修改后的采購管理
public?class?Purchase?extends?AbstractColleague{
?????public?Purchase(AbstractMediator?_mediator){
?????????????super(_mediator);
?????}
?????//采購IBM電腦
?????public?void?buyIBMcomputer(int?number){
?????????????super.mediator.execute("purchase.buy",?number);
?????}
?????//不再采購IBM電腦
?????public?void?refuseBuyIBM(){
?????????????System.out.println("不再采購IBM電腦");
?????}
}
上述Purchase類簡化了很多,也清晰了很多,處理自己的職責,與外界有關系的事件處理則交給了中介者來完成。再來看Stock類,如代碼清單14-9所示。
代碼清單14-9 修改后的庫存管理
public?class?Stock?extends?AbstractColleague?{
?????public?Stock(AbstractMediator?_mediator){
?????????????super(_mediator);
?????}
?????//剛開始有100臺電腦
?????private?static?int?COMPUTER_NUMBER?=100;
?????//庫存增加
?????public?void?increase(int?number){
?????????????COMPUTER_NUMBER?=?COMPUTER_NUMBER?+?number;
?????????????System.out.println("庫存數量為:"+COMPUTER_NUMBER);
?????}
?????//庫存降低
?????public?void?decrease(int?number){
?????????????COMPUTER_NUMBER?=?COMPUTER_NUMBER?-?number;
?????????????System.out.println("庫存數量為:"+COMPUTER_NUMBER);
?????}
?????//獲得庫存數量
?????public?int?getStockNumber(){
?????????????return?COMPUTER_NUMBER;
?????}
?????//存貨壓力大了,就要通知采購人員不要采購,銷售人員要盡快銷售
?????public?void?clearStock(){
?????????????System.out.println("清理存貨數量為:"+COMPUTER_NUMBER);
?????????????super.mediator.execute("stock.clear");
?????}
}
銷售管理Sale類如代碼清單14-10所示。
代碼清單14-10 修改后的銷售管理
public?class?Sale?extends?AbstractColleague?{
?????public?Sale(AbstractMediator?_mediator){
?????????????super(_mediator);
?????}
?????//銷售IBM電腦
?????public?void?sellIBMComputer(int?number){
?????????????super.mediator.execute("sale.sell",?number);
?????????????System.out.println("銷售IBM電腦"+number+"臺");
?????}
?????//反饋銷售情況,0~100變化,0代表根本就沒人買,100代表非常暢銷,出一個賣一個
?????public?int?getSaleStatus(){
?????????????Random?rand?=?new?Random(System.currentTimeMillis());
?????????????int?saleStatus?=?rand.nextInt(100);
?????????????System.out.println("IBM電腦的銷售情況為:"+saleStatus);
?????????????return?saleStatus;
?????}
?????//折價處理
?????public?void?offSale(){
?????????????super.mediator.execute("sale.offsell");
?????}
}
增加了中介者,場景類也需要小小的改動,如代碼清單14-11所示。
代碼清單14-11 修改后的場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????AbstractMediator?mediator?=?new?Mediator();
?????????????//采購人員采購電腦
?????????????System.out.println("------采購人員采購電腦--------");
?????????????Purchase?purchase?=?new?Purchase(mediator);
?????????????purchase.buyIBMcomputer(100);??????????????
?????????????//銷售人員銷售電腦
?????????????System.out.println("\n------銷售人員銷售電腦--------");
?????????????Sale?sale?=?new?Sale(mediator);
?????????????sale.sellIBMComputer(1);
?????????????//庫房管理人員管理庫存
?????????????System.out.println("\n------庫房管理人員清庫處理--------");
?????????????Stock?stock?=?new?Stock(mediator);
?????????????stock.clearStock();
?????}
}
在場景類中增加了一個中介者,然后分別傳遞到三個同事類中,三個類都具有相同的特性:只負責處理自己的活動(行為),與自己無關的活動就丟給中介者處理,程序運行的結果是相同的。從項目設計上來看,加入了中介者,設計結構清晰了很多,而且類間的耦合性大大減少,代碼質量也有了很大的提升。
在多個對象依賴的情況下,通過加入中介者角色,取消了多個對象的關聯或依賴關系,減少了對象的耦合性。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——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種設計模式彩圖