設計模式之(六)------適配器模式,設計模式------
【摘自 您的設計模式】這個模式也很簡單,你筆記本上的那個拖在外面的黑盒子就是個適配器,一般你在中國能用,在日本也能用,
雖然兩個國家的的電源電壓不同,中國是 220V,日本是 110V,但是這個適配器能夠把這些不同的電壓轉換
為你需要的 36V 電壓,保證你的筆記本能夠正常運行,那我們在設計模式中引入這個適配器模式是不是也
是這個意思呢?是的,一樣的作用,兩個不同介面,有不同的實現,但是某一天突然上帝命令你把 B 介面
轉換為 A 介面,怎麼辦?繼承,能解決,但是比較傻,而且還違背了 OCP 原則,怎麼辦?好在我們還有適
配器模式。
我在 2004 年的時候帶了一個項目,做一個人力資源管理,該項目是我們總公司發起的項目,公司一共
有 700 多號人,包括子公司,這個項目還是比較簡單的,分為三大模組:人員資訊管理,薪酬管理,職位
管理,其中人員管理這塊就用到了適配器模式,是怎麼回事呢?當時開發時明確的指明:人員資訊簡管理
的對象是所有員工的所有資訊,然後我們就這樣設計了一個類圖:
還是比較簡單的,有一個對象 UserInfo 儲存使用者的所有資訊(實際系統上還有很多子類,不多說了),
也就是 BO(Business Object),這個對象設計為貧血對象(Thin Business Object),不需要儲存狀態以及
相關的關係,而且我也是反對使用充血對象(Rich Business Object),這裡說了兩個名詞貧血對象和充血
對象,這兩個名詞很簡單,在領域模型中分別叫做貧血領域模型和充血領域模型,有什麼區別呢?在一個
對象中不儲存實體狀態以及對象之間的關係的就叫做貧血對象,上升到領域模型中就是貧血領域模型,有
實體狀態和對象關係的模型的就是充血領域模型,是不是太技術化了?這個看不懂沒關係,都是糊弄人的
東西,屬於專用名詞,這本書寫完了,我再折騰本領域模型的文章,揭露領域模型中糊弄人的專有名詞,
這個絕對是專有名詞的堆砌,呵呵。扯遠了,我們繼續說適配器模式,這個 UserInfo 對象,在系統中很多
地方使用,你可以查看自己的資訊,也可以做修改,當然這個對象是有 setter 方法的,我們這裡用不到就
隱藏掉了。
這個項目是 04 年年底投產的,運行到 05 年年底還是比較平穩的,中間修修補補也很正常,05 年年底
不知道是那股風吹的,很多公司開始使用借聘人員的方式招聘人員,我們公司也不例外,從一個人力資源
公司借用了一大批的低技術、低工資的人員,分配到各個子公司,總共有將近 200 號人,然後就找我們部
門老大談判,說要增加一個功能借用人員管理,老大一看有錢賺呀,一拍大腿,做!
我帶人過去一調研,不是這麼簡單,人力資源公司有一套自己的人員管理系統,我們公司需要把我們
使用到的人員資訊傳輸到我們的系統中,系統之間的傳輸使用 RMI(Remote Method Invocation,遠程對象
調用)的方式,但是有一個問題人力資源公司的人員對象和我們系統的對象不相同呀,他們的對象是這樣
的:
人員資源公司是把人的資訊分為了三部分:基本資料,辦公資訊和個人家庭資訊,並且都放到了 HashMap
中,比如人員的姓名放到 BaseInfo 資訊中,家庭地址放到 HomeInfo 中,這咱不好說他們系統設計的不好,
那問題是咱的系統要和他們系統有互動,怎麼辦?使用適配器模式,類圖如下:
大家可能會問,這兩個對象都不在一個系統中,你如何使用呢?簡單!RMI 已經幫我們做了這件事情,
只要有介面,就可以把遠端對象當成本地的對象使用,這個大家有時間可以去看一下 RMI 文檔,不多說
了。通過適配器,把 OuterUser 偽裝成我們系統中一個 IUserInfo 對象,這樣,我們的系統基本不用修改
什麼程式員,所有的人員查詢、調用跟本地一樣樣的,說的口乾舌燥,那下邊我們來看具體的代碼實現:
首先看 IUserInfo.java 的代碼:
package com.cbf4life;/*** @author cbf4Life cbf4life@126. com* I'm glad to share my knowledge with you all.* 使用者資訊對象*/public interface IUserInfo {//獲得使用者姓名public String getUserName();//獲得家庭地址public String getHomeAddress();//手機號碼,這個太重要,手機泛濫呀public String getMobileNumber();//辦公電話,一般式有線電話public String getOfficeTelNumber();//這個人的職位是啥public String getJobPosition();//獲得家庭電話,這個有點缺德,我是不喜歡打家庭電話討論工作public String getHomeTelNumber();}
然後看這個介面的實作類別:
package com.cbf4life;/*** @author cbf4Life cbf4life@126. com* I'm glad to share my knowledge with you all.*/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; }}
可能有人要問了,為什麼要把電話號碼、手機號碼都設定成 String 類型,而不是 int 類型,大家覺的
呢?題外話,這個絕對應該是 String 類型,包括資料庫也應該是 varchar 類型的,手機號碼有小靈通帶區
號的,比如 02100001,這個你用數字怎麼表示?有些人要在手機號碼前加上 0086 後再儲存,比如我們公司
的印度阿三就是這樣,喜歡在手機號碼前 0086 儲存下來,呵呵,我是想到啥就說啥,囉嗦了點。繼續看我
們的代碼,下面看我們系統的應用如何調用 UserInfo 的資訊:
package com.cbf4life;/*** @author cbf4Life cbf4life@126. com* I'm glad to share my knowledge with you all.* 這就是我們具體的應用了,比如老闆要查所有的20-30的女性資訊*/public class App {public static void main(String[] args) { //沒有與外系統串連的時候,是這樣寫的 IUserInfo youngGirl = new UserInfo(); //從資料庫中查到101個 for(int i=0;i<101;i++){ youngGirl.getMobileNumber(); } }}
這老闆,比較那個,為什麼是 101,是男生都應該知道吧,111 代表男生,101 代表女生,呵呵,是不
是比較色呀。從資料庫中產生了 101 個 UserInfo 對象,直接列印出來就成了。那然後增加了外系統的人員
資訊,怎麼處理呢?下面是 IOuterUser.java 的原始碼:
public interface IOuterUser {//基本資料,比如名稱,性別,手機號碼了等public Map getUserBaseInfo();//工作區域資訊public Map getUserOfficeInfo();//使用者的家庭資訊public Map getUserHomeInfo();}我們再看看外系統的使用者資訊的具體實作類別:package com.cbf4life;import java.util.HashMap;import java.util.Map;/*** @author cbf4Life cbf4life@126. com* I'm glad to share my knowledge with you all.* 外系統的使用者資訊的實作類別*/@SuppressWarnings("all")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; }}
那怎麼把外系統的使用者資訊封裝成我們公司的人員資訊呢?看下面的 OuterUserInfo 類源碼,也就是
我們的適配器:
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; }}
這個適配器的作用就是做介面的轉換,
那然後我們再來看看我們的業務是怎麼調用的:
public class App {public static void main(String[] args) { //沒有與外系統串連的時候,是這樣寫的 //IUserInfo youngGirl = new UserInfo(); //老闆一想不對呀,兔子不吃窩邊草,還是找人力資源的員工好點 IUserInfo youngGirl = new OuterUserInfo(); //我們只修改了這一句好 //從資料庫中查到101個 for(int i=0;i<101;i++){ youngGirl.getMobileNumber(); } }}
大家看,使用了適配器模式只修改了一句話,其他的商務邏輯都不用修改就解決了系統對接的問題,
而且在我們實際系統中只是增加了一個業務類的繼承,就實現了可以查本公司的員工資訊,也可以查人力
資源公司的員工資訊,盡量少的修改,通過擴充的方式解決了該問題。
適配器模式分為類適配器和對象適配器,這個區別不大,上邊的例子就是類適配器,那對象適配器是
什麼樣子呢?對象適配器的類圖是這個樣子滴:
看到沒?和上邊的類圖就一個箭頭的圖形的差異,一個是繼承,一個是關聯,就這麼多區別,只要把
我們上面的程式稍微修改一下就成了類適配器,這個大家自己考慮一下,簡單的很。
適配器模式不適合在系統設計階段採用,沒有一個系統分析師會在做詳設的時候考慮使用適配器模式,
這個模式使用的主要情境是擴充應用中,就像我們上面的那個例子一樣,系統擴充了,不符合原有設計的
時候才考慮通過適配器模式減少代碼修改帶來的風險。