第19章 適配器模式
19.1 業務發展——上帝才能控制
有這樣一句名言:“智者千慮必有一失,愚者千慮必有一得”[[1]](#),意思是說不管多聰明的人,經過多少次的思考,也總是會出現一些微小的錯誤,“智者”都是如此,何況我們這些平庸之輩呢!我們在進行系統開發時,不管之前的可行性分析、需求分析、系統設計處理得多么完美,總會在關鍵時候、關鍵場合出現一些“意外”。對于這些“意外”,該來的還是要來,躲是躲不過去的,那我們怎么來彌補這些“意外”呢?這難不倒我們的設計大師,他們創造出了一些補救模式,今天我們就來講一個補救模式,這種模式可以讓你從因業務擴展而系統無法迅速適應的苦惱中解脫而出。
2004年我帶了一個項目,做一個人力資源管理項目,該項目是我們總公司發起的,公司一共有700多號人。這個項目還是比較簡單的,分為三大模塊:人員信息管理、薪酬管理、職位管理。當時開發時業務人員明確指明:人員信息管理的對象是所有員工的所有信息,所有的員工指的是在職的員工,其他的離職的、退休的暫不考慮。根據需求我們設計了如圖19-1所示的類圖。

圖19-1 人員信息類圖
非常簡單,有一個對象UserInfo存儲用戶的所有信息(實際系統上還有很多子類,不多說了),也就是BO(Business Object,業務對象),這個對象設計為貧血對象(Thin Business Object),不需要存儲狀態以及相關的關系,本人是反對使用充血對象(Rich Business Object),這里提到兩個名詞:貧血對象和充血對象,這兩個名詞很簡單,在領域模型中分別叫做貧血領域模型和充血領域模型,有什么區別呢?一個對象如果不存儲實體狀態以及對象之間的關系,該對象就叫做貧血對象,對應的領域模型就是貧血領域模型,有實體狀態和對象關系的模型就是充血領域模型。看不懂沒關系,都是糊弄人的東西,屬于專用名詞。扯遠了,我們繼續說我們的人力資源管理項目,這個UserInfo對象,在系統中很多地方使用,你可以查看自己的信息,也可以修改,當然這個對象是有setter方法的,我們這里用不到就隱藏掉了。先來看接口,員工信息接口如代碼清單19-1所示。
代碼清單19-1 員工信息接口
public?interface?IUserInfo?{
?????//獲得用戶姓名
?????public?String?getUserName();
?????//獲得家庭地址
?????public?String?getHomeAddress();
?????//手機號碼,這個太重要,手機泛濫呀
?????public?String?getMobileNumber();
?????//辦公電話,一般是座機
?????public?String?getOfficeTelNumber();
?????//這個人的職位是什么
?????public?String?getJobPosition();
?????//獲得家庭電話,這有點不好,我不喜歡打家庭電話討論工作
?????public?String?getHomeTelNumber();
}
員工信息接口有了,就需要設計一個實現類來容納數據,如代碼清單19-2所示。
代碼清單19-2 實現類
public?class?UserInfo?implements?IUserInfo?{
?????/*?
??????*?獲得家庭地址,下屬送禮也可以找到地方
??????*/
?????public?String?getHomeAddress()?{
?????????????System.out.println("這里是員工的家庭地址...");
?????????????return?null;
?????}
?????/*?
??????*?獲得家庭電話號碼
??????*/
?????public?String?getHomeTelNumber()?{
?????????????System.out.println("員工的家庭電話是...");
?????????????return?null;
?????}
?????/*?
??????*?員工的職位,是部門經理還是普通職員
??????*/
?????public?String?getJobPosition()?{
?????????????System.out.println("這個人的職位是BOSS...");
?????????????return?null;
?????}
?????/*?
??????*?手機號碼
??????*/
?????public?String?getMobileNumber()?{
?????????????System.out.println("這個人的手機號碼是0000...");
?????????????return?null;
?????}
?????/*?
??????*?辦公室電話,煩躁的時候最好"不小心"把電話線踢掉
??????*/
?????public?String?getOfficeTelNumber()?{
?????????????System.out.println("辦公室電話是...");
?????????????return?null;
?????}
?????/*?
??????*?姓名,這個很重要
??????*/
?????public?String?getUserName()?{
?????????????System.out.println("姓名叫做...");
?????????????return?null;
?????}
}
這個項目是2004年年底投產的,運行到2005年年底還是比較平穩的,中間修修補補也很正常,2005年年底不知道是哪股風吹的,很多公司開始使用借聘人員的方式引進人員,我們公司也不例外,從一個勞動資源公司借用了一大批的低技術、低工資的人員,分配到各個子公司,總共有將近200人,然后人力資源部就找我們部門老大談判,說要增加一個功能:借用人員管理,老大一看有錢賺呀,一拍大腿,做!
老大命令一下來,我立馬帶人過去調研,需求就一句話,但是真深入地調研還真不是那么簡單。借聘人員雖然在我們公司干活,和我們一個樣,干活時沒有任何的差別,但是他們的人員信息、工資情況、福利情況等都是由勞動服務公司管理的,并且有一套自己的人員管理系統,人力資源部門就要求我們系統同步勞動服務公司的信息,當然是只要在我們公司工作的人員信息,其他人員信息是不需要的,而且還要求信息同步,也就是:勞動服務公司的人員信息一變更,我們系統就應該立刻體現出來,為什么要即時而不批量呢?是因為我們公司與勞動服務公司之間是按照人頭收費的,甭管是什么人,只要我們公司借用,就這個價格,我要一個研究生,你派了一個高中生給我,那算什么事?因此,了解了業務需求用后,項目組決定采用RMI(Remote Method Invocation,遠程對象調用)的方式進行聯機交互,但是深入分析后,一個重大問題立刻顯現出來:勞動服務公司的人員對象和我們系統的對象不相同,他們的對象如下所示。

圖19-2 勞動服務公司的人員信息類圖
勞動服務公司是把人員信息分為了三部分:基本信息、辦公信息和個人家庭信息,并且都放到了HashMap中,比如人員的姓名放到BaseInfo信息中,家庭地址放到HomeInfo中,這也是一個可以接受的模式,我們來看看他們的代碼,接口如代碼清單19-3所示。
代碼清單19-3 勞動服務公司的人員信息接口
public?interface?IOuterUser?{??????
?????//基本信息,比如名稱、性別、手機號碼等
?????public?Map?getUserBaseInfo();
?????//工作區域信息
?????public?Map?getUserOfficeInfo();
?????//用戶的家庭信息
?????public?Map?getUserHomeInfo();
}
勞動服務公司的人員信息是這樣存放的,如代碼清單19-4所示。
代碼清單19-4 勞動服務公司的人員實現
public?class?OuterUser?implements?IOuterUser?{
?????/*?
??????*?用戶的基本信息
??????*/
?????public?Map?getUserBaseInfo()?{
?????????????HashMap?baseInfoMap?=?new?HashMap();
?????????????baseInfoMap.put("userName",?"這個員工叫混世魔王...");
?????????????baseInfoMap.put("mobileNumber",?"這個員工電話是...");
?????????????return?baseInfoMap;
?????}
?????/*?
??????*?員工的家庭信息
??????*/
?????public?Map?getUserHomeInfo()?{
?????????????HashMap?homeInfo?=?new?HashMap();
?????????????homeInfo.put("homeTelNumbner",?"員工的家庭電話是...");
?????????????homeInfo.put("homeAddress",?"員工的家庭地址是...");
?????????????return?homeInfo;
?????}
?????/*?
??????*?員工的工作信息,比如,職位等
??????*/
?????public?Map?getUserOfficeInfo()?{
?????????????HashMap?officeInfo?=?new?HashMap();
?????????????officeInfo.put("jobPosition","這個人的職位是BOSS...");
?????????????officeInfo.put("officeTelNumber",?"員工的辦公電話是...");
?????????????return?officeInfo;
?????}
}
看到這里,咱不好說他們系統設計得不好,問題是咱的系統要和他們的系統進行交互,怎么辦?我們不可能為了這一小小的功能而對我們已經運行良好系統進行大手術,那怎么辦?我們可以轉化,先拿到對方的數據對象,然后轉化為我們自己的數據對象,中間加一層轉換處理,按照這個思路,我們設計了如圖19-3所示的類圖。

圖19-3 增加了中轉處理的人員信息類圖
大家可能會問,這兩個對象都不在一個系統中,你如何使用呢?簡單!RMI已經幫我們做了這件事情,只要有接口,就可以把遠程的對象當成本地的對象使用,這個大家有時間可以去看一下RMI文檔,不多說了。OuterUserInfo可以看做是“兩面派”,實現了IUserInfo接口,還繼承了OuterUser,通過這樣的設計,把OuterUser偽裝成我們系統中一個IUserInfo對象,這樣,我們的系統基本不用修改,所有的人員查詢、調用跟本地一樣。
注意 我們之所以能夠增加一個OuterUserInfo中轉類,是因為我們在系統設計時嚴格遵守了依賴倒置原則和里氏替換原則,否則即使增加了中轉類也無法解決問題。
說得口干舌燥,下邊我們來看具體的代碼實現,中轉角色OuterUserInfo如代碼清單19-5所示。
代碼清單19-5 中轉角色
public?class?OuterUserInfo?extends?OuterUser?implements?IUserInfo?{
?????private?Map?baseInfo?=?super.getUserBaseInfo();??//員工的基本信息
?????private?Map?homeInfo?=?super.getUserHomeInfo();?//員工的家庭信息
?????private?Map?officeInfo?=?super.getUserOfficeInfo();?//工作信息
?????/*?
??????*?家庭地址
??????*/
?????public?String?getHomeAddress()?{
?????????????String?homeAddress?=?(String)this.homeInfo.get("homeAddress");
?????????????System.out.println(homeAddress);
?????????????return?homeAddress;
?????}
?????/*?
??????*?家庭電話號碼
??????*/
?????public?String?getHomeTelNumber()?{
?????????????String?homeTelNumber?=?(String)this.homeInfo.get("homeTelNumber");
?????????????System.out.println(homeTelNumber);
?????????????return?homeTelNumber;
?????}
?????/*?
??????*職位信息
??????*/
?????public?String?getJobPosition()?{
?????????????String?jobPosition?=?(String)this.officeInfo.get("jobPosition");
?????????????System.out.println(jobPosition);
?????????????return?jobPosition;
?????}
?????/*?
??????*?手機號碼
??????*/
?????public?String?getMobileNumber()?{
?????????????String?mobileNumber?=?(String)this.baseInfo.get("mobileNumber");
?????????????System.out.println(mobileNumber);
?????????????return?mobileNumber;
?????}
?????/*?
??????*?辦公電話
??????*/
?????public?String?getOfficeTelNumber()?{
?????????????String?officeTelNumber?=?(String)this.officeInfo.get("officeTelNumber");
?????????????System.out.println(officeTelNumber);
?????????????return?officeTelNumber;
?????}
?????/*?
??????*?員工的名稱
??????*/
?????public?String?getUserName()?{
?????????????String?userName?=?(String)this.baseInfo.get("userName");
?????????????System.out.println(userName);
?????????????return?userName;
?????}
}
大家看到沒?中轉的角色有很多的強制類型轉換,就是(String)這個東西,如果使用泛型的話,就可以完全避免這個轉化(當然了,泛型當時還沒有誕生)。我們要看看這個中轉是否真的起到了中轉的作用,我們想象這樣一個場景:公司大老板想看看我們自己公司年輕女孩子的電話號碼,那該場景類就如代碼清單19-6所示。
代碼清單19-6 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//沒有與外系統連接的時候,是這樣寫的
?????????????IUserInfo?youngGirl?=?new?UserInfo();
?????????????//從數據庫中查到101個
?????????????for(int?i=0;i<101;i++){
????????????????????youngGirl.getMobileNumber();
?????????????}
?????}
}
這老板比較色呀。從數據庫中生成了101個UserInfo對象,直接打印出來就成了。老板回頭一想,不對呀,兔子不吃窩邊草,還是調取借用人員看看,于是要查詢出借用人員中美女的電話號碼,如代碼清單19-7所示。
代碼清單19-7 查看勞動服務公司人員信息場景
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//老板一想不對呀,兔子不吃窩邊草,還是找借用人員好點
?????????????//我們只修改了這句話
?????????????IUserInfo?youngGirl?=?new?OuterUserInfo();??
?????????????//從數據庫中查到101個
?????????????for(int?i=0;i<101;i++){
????????????????????youngGirl.getMobileNumber();
?????????????}
?????}
}
大家看,使用了適配器模式只修改了一句話,其他的業務邏輯都不用修改就解決了系統對接的問題,而且在我們實際系統中只是增加了一個業務類的繼承,就實現了可以查本公司的員工信息,也可以查人力資源公司的員工信息,盡量少的修改,通過擴展的方式解決了該問題。這就是適配模式。
[[1]](#)出自《史記·卷九十二》。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——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種設計模式彩圖