38.1 規格模式
38.1.1 規格模式的實現
不知道諸位有沒有使用C#3.5做過開發,它有一個非常重要的新特性——LINQ(Language INtegrated Query,語言集成查詢),它提供了類似于SQL語法的遍歷、篩選等功能,能完成對對象的查詢,就像通過SQL語句查詢數據庫一樣,例如這樣的一個程序片段:
Dim?DataList?As?String()?=?{"abc",?"def",?"ght"}
Dim?Result?=?From?T?As?String?In?DataList?Where?T?=?"abc"
這句話的意思就是從一個數組中查找出值為abc的元素,返回結果為IEnumerable,枚舉器類型。注意看第二句話,它使用了類似SQL的Select語法結構,from、where關鍵字都有了,而且還支持類似的Orderby、Groupby功能,很強大,有興趣的讀者可以查閱有關資料。那在Java世界中是否也存在這樣的輔助框架呢?有,JoSQL、Quaere都可以提供類似的LINQ語言,讀者可以到網上研究一下JavaDoc,同樣非常簡單,功能強大。
我們今天要講的主題與LINQ有很大關系,它是實現LINQ的核心。想想SQL語句中什么是最復雜的,是where后面的查詢條件,看看自己寫的SQL語句基本上都是一長串的條件判斷,中間一堆的and、or、not邏輯符。我們今天的任務就是要實現條件語句的解析,該部分實現了,基本上LINQ語法已經實現了一大半。
我們以一個案例來講解該技術,在內存中有10個User對象,根據不同的條件查找出用戶,比如姓名包含某個字符、年齡小于多少歲等條件,類似這樣的SQL:
Select?*?From?User?where?name?like?'%國慶%'
查找出姓名中包含“國慶”兩個字的用戶,這在關系型數據庫中很容易實現,但是在對象群中怎么實現這樣的查詢呢?好,看似很簡單,先設計一個用戶類,然后提供一個用戶查找工具類,類圖非常容易,如圖38-1所示。
很簡單的類圖,有一個用戶類,同時提供了一個操作用戶的輔助類,我們先來看User類,如代碼清單38-1所示。

圖38-1 簡單用戶查詢類圖
代碼清單38-1 用戶類
public?class?User?{
?????//姓名
?????private?String?name;
?????//年齡
?????private?int?age;
?????public?User(String?_name,int?_age){
?????????????this.name?=?_name;
?????????????this.age?=?_age;
?????}
?????public?String?getName()?{
?????????????return?name;
?????}
?????public?void?setName(String?name)?{
?????????????this.name?=?name;
?????}
?????public?int?getAge()?{
?????????????return?age;
?????}
?????public?void?setAge(int?age)?{
?????????????this.age?=?age;
?????}?????
?????//用戶信息打印
?????@Override
?????public?String?toString(){
?????????????return?"用戶名:"?+?name+"\t年齡:"?+?age;
?????}
}
User就是一個簡單BO業務對象,再來看用戶操作接口,它定義一個用戶操作類必須具有的方法,如代碼清單38-2所示。
代碼清單38-2 用戶操作對象接口
public?interface?IUserProvider?{
?????//根據用戶名查找用戶
?????public?ArrayList<User>?findUserByNameEqual(String?name);
?????//年齡大于指定年齡的用戶
?????public?ArrayList<User>?findUserByAgeThan(int?age);
}
在這里只定義了兩個查詢實現,分別是名字相同的用戶和年齡大于指定年齡的用戶,大家都知道,相似的查詢條件還有很多,比如名字中包含指定字符、年齡小于指定年齡等,我們僅以實現這兩個查詢作為代表,如代碼清單38-3所示。
代碼清單38-3 用戶操作類
public?class?UserProvider?implements?IUserProvider?{
?????//用戶列表
?????private?ArrayList<User>?userList;
?????//構造函數傳遞用戶列表
?????public?UserProvider(ArrayList<User>?_userList){
?????????????this.userList?=?_userList;
?????}
?????//年齡大于指定年齡的用戶
?????public?ArrayList<User>?findUserByAgeThan(int?age)?{
?????????????ArrayList<User>?result?=?new?ArrayList<User>();
?????????????for(User?u:userList){
?????????????????????if(u.getAge()>age){?//符合條件的用戶
??????????????????????????result.add(u);
?????????????????????}
?????????????}
?????????????return?result;
?????}
?????//姓名等于指定姓名的用戶
?????public?ArrayList<User>?findUserByNameEqual(String?name)?{
?????????????ArrayList<User>?result?=?new?ArrayList<User>();
?????????????for(User?u:userList){
??????????????????????if(u.getName().equals(name)){//符合條件
??????????????????????????????result.add(u);
??????????????????????}
?????????????}
?????????????return?result;
?????}
}
通過for循環遍歷一個動態數組,判斷用戶是否符合條件,將符合條件的用戶放置到另外一個數組中,比較簡單。我們編寫場景類來模擬該情景,如代碼清單38-4所示。
代碼清單38-4 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//首先初始化一批用戶
?????????????ArrayList<User>?userList?=?new?ArrayList<User>();
?????????????userList.add(new?User("蘇大",3));
?????????????userList.add(new?User("牛二",8));
?????????????userList.add(new?User("張三",10));
?????????????userList.add(new?User("李四",15));
?????????????userList.add(new?User("王五",18));
?????????????userList.add(new?User("趙六",20));
?????????????userList.add(new?User("馬七",25));
?????????????userList.add(new?User("楊八",30));
?????????????userList.add(new?User("侯九",35));
?????????????userList.add(new?User("布十",40));
?????????????//定義一個用戶查詢類
?????????????IUserProvider?userProvider?=?new?UserProvider(userList);
?????????????//打印出年齡大于20歲的用戶
?????????????System.out.println("===年齡大于20歲的用戶===");
?????????????for(User?u:userProvider.findUserByAgeThan(20)){
??????????????????????System.out.println(u);
?????????????}
?????}
}
運行結果如下所示:
===年齡大于20歲的用戶===
用戶名:馬七 年齡:25
用戶名:楊八 年齡:30
用戶名:侯九 年齡:35
用戶名:布十 年齡:40
結果非常正確,但是這樣的一個框架基本上是不能適應業務變化的,為什么呢?業務變化雖然無規則,但是可以預測,比如我們這個查詢,今天要查找年齡大于20歲的用戶,明天要查找年齡小于30歲的用戶,后天要查找姓名中包含“國慶”兩個字的用戶,想想看IUserProvider接口是不是要一直修改下去?接口是契約,而且我們一直提倡面向接口編程,但是在這里接口竟然都可以修改,是不是發現設計有很大問題了!
問題發現了,就要想辦法解決。再回顧一下編寫的代碼,注意看findUserByAgeThan和findUserByNameEqual兩個方法,兩者的代碼有什么不同呢?除了if后面的判斷條件不同外,就沒有不同的地方了,我們一直在說封裝變化,這兩段程序就僅僅有這一個變化點,我們是不是可以把它封裝起來呢?完全可以,把它們兩者的共同點抽取出來,先修改一下接口,如代碼清單38-5所示。
代碼清單38-5 修正后的接口
public?interface?IUserProvider?{
?????//根據條件查找用戶
?????public?ArrayList<User>?findUser(boolean?condition);
}
這個接口的設計想法非常好,但是參數condition很難實現,看看findUserByAgeThan、findUserByNameEqual這兩個方法,怎么才能把兩者的不同點設置成一個布爾型呢?如果需要在IUserProvider對象外判斷后傳遞進來,那我們的封裝就沒有任何意義了——目前為止,這個方案有問題了。
繼續考慮,既然不能在封裝外運算,那就把整個條件都進行封裝,由IUserProvider自己實現運算。好方法!那我們就設計一個這樣的類,我們叫它規格類,什么意思呢?它是對一批對象的說明性描述,它依照基準判斷候選對象是否滿足條件。
思考后,我們設計出類圖,如圖38-2所示。

圖38-2 加入規格后的設計類圖
在該類圖中建立了一個規格書接口,它的作用就是定制各種各樣的規格,比如名字相等的規格UserByNameEqual、年齡大于基準年齡的規格UserByAgeThan等,然后在用戶操作類中采用該規格進行判斷。User類沒有任何改變,如代碼清單38-1所示,不再贅述。
規格書接口是對全體規格書的聲明定義,如代碼清單38-6所示。
代碼清單38-6 規格書接口
public?interface?IUserSpecification?{
?????//候選者是否滿足要求
?????public?boolean?isSatisfiedBy(User?user);?????
}
規格書接口只定義一個方法,判斷候選用戶是否滿足條件。再來看姓名相同的規格書,它實現了規格書接口,如代碼清單38-7所示。
代碼清單38-7 姓名相同的規格書
public?class?UserByNameEqual?implements?IUserSpecification?{
?????//基準姓名
?????private?String?name;
?????//構造函數傳遞基準姓名
?????public?UserByNameEqual(String?_name){
?????????????this.name?=?_name;
?????}
?????//檢驗用戶是否滿足條件
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????return?user.getName().equals(name);
?????}
}
代碼很簡單,通過構造函數傳遞進來基準用戶名,然后判斷候選用戶是否匹配。大于基準年齡的規格書與此類似,如代碼清單38-8所示。
代碼清單38-8 大于基準年齡的規格書
public?class?UserByAgeThan?implements?IUserSpecification?{
?????//基準年齡
?????private?int?age;
?????//構造函數傳遞基準年齡
?????public?UserByAgeThan(int?_age){
?????????????this.age?=?_age;
?????}
?????//檢驗用戶是否滿足條件
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????return?user.getAge()?>?age;
?????}
}
規格書都已經定義完畢,我們再來看用戶操作類,先看用戶操作的接口,如代碼清單38-9所示。
代碼清單38-9 用戶操作接口
public?interface?IUserProvider?{
?????//根據條件查找用戶
?????public?ArrayList<User>?findUser(IUserSpecification?userSpec);
}
只有一個方法——根據指定的規格書查找用戶。再來看其實現類,如代碼清單38-10所示。
代碼清單38-10 用戶操作
public?class?UserProvider?implements?IUserProvider?{
?????//用戶列表
?????private?ArrayList<User>?userList;
?????//傳遞用戶列表
?????public?UserProvider(ArrayList<User>?_userList){
?????????????this.userList?=?_userList;
?????}
?????//根據指定的規格書查找用戶
?????public?ArrayList<User>?findUser(IUserSpecification?userSpec)?{
?????????????ArrayList<User>?result?=?new?ArrayList<User>();
?????????????for(User?u:userList){
?????????????????????if(userSpec.isSatisfiedBy(u)){//符合指定規格
??????????????????????????result.add(u);
??????????????????}
?????????????}
?????????????return?result;
?????}
}
程序改動很小,僅僅在if判斷語句中根據規格書進行判斷,我們持續地擴展規格書,有多少查詢分類就可以擴展出多少個實現類,而IUserProvider則不需要任何改動,它的一個方法就覆蓋了我們剛剛提出的N多查詢路徑。我們設計一個場景來看看效果如何,如代碼清單38-11所示。
代碼清單38-11 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//首先初始化一批用戶
?????????????ArrayList<User>?userList?=?new?ArrayList<User>();
?????????????userList.add(new?User("蘇大",3));
?????????????userList.add(new?User("牛二",8));
?????????????userList.add(new?User("張三",10));
?????????????userList.add(new?User("李四",15));
?????????????userList.add(new?User("王五",18));
?????????????userList.add(new?User("趙六",20));
?????????????userList.add(new?User("馬七",25));
?????????????userList.add(new?User("楊八",30));
?????????????userList.add(new?User("侯九",35));
?????????????userList.add(new?User("布十",40));?????
?????????????//定義一個用戶查詢類
?????????????IUserProvider?userProvider?=?new?UserProvider(userList);
?????????????//打印出年齡大于20歲的用戶
?????????????System.out.println("===年齡大于20歲的用戶===");
?????????????//定義一個規格書
?????????????IUserSpecification?userSpec?=?new?UserByAgeThan(20);
?????????????for(User?u:userProvider.findUser(userSpec)){
?????????????????????System.out.println(u);
?????????????}
?????}
}
在場景類中定義了一個規格書,然后把規格書提交給UserProvider就可以查找到自己需要的用戶了,運行結果相同,不再贅述。
大家想想看,如果現在需求變更了,比如需要一個年齡小于基準年齡的用戶,該怎么修改?增加一個小于基準年齡的規格書,實現IUserSpecification接口,然后在新的業務中調用即可,別的什么都不需要修改。再比如需要一個類似SQL中like語句的處理邏輯,這個也不難,如代碼清單38-12所示。
代碼清單38-12 Like規格書
public?class?UserByNameLike?implements?IUserSpecification?{
?????//like的標記
?????private?final?static?String?LIKE_FLAG?=?"%";
?????//基準的like字符串
?????private?String?likeStr;
?????//構造函數傳遞基準姓名
?????public?UserByNameLike(String?_likeStr){
?????????????this.likeStr?=?_likeStr;
?????}
?????//檢驗用戶是否滿足條件
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????boolean?result?=?false;
?????????????String?name?=?user.getName();
?????????????//替換掉%后的干凈字符串
?????????????String?str?=?likeStr.replace("%","");
?????????????//是以名字開頭,如'國慶%'
?????????????if(likeStr.endsWith(LIKE_FLAG)?&&?!likeStr.startsWith(LIKE_FLAG)){
??????????????????result?=?name.startsWith(str);
?????????????}else?if(likeStr.startsWith(LIKE_FLAG)?&&?!likeStr.endsWith(LIKE_FLAG)){?//類似?'%國慶'
??????????????????result?=?name.endsWith(str);
?????????????}else{
??????????????????result?=?name.contains(str);?//類似于'%國慶%'
?????????????}
?????????????return?result;
?????}
}
同時,場景類也要適當地改動,畢竟業務已經發生了變化,高層模塊要適應這種變化,如代碼清單38-13所示。
代碼清單38-13 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//首先初始化一批用戶
?????????????ArrayList<User>?userList?=?new?ArrayList<User>();
?????????????userList.add(new?User("蘇國慶",23));
?????????????userList.add(new?User("國慶牛",82));
?????????????userList.add(new?User("張國慶三",10));
?????????????userList.add(new?User("李四",10));
?????????????//定義一個用戶查詢類
?????????????IUserProvider?userProvider?=?new?UserProvider(userList);
?????????????//打印出名字包含"國慶"的人員
?????????????System.out.println("===名字包含國慶的人員===");
?????????????//定義一個規格書
?????????????IUserSpecification?userSpec?=?new?UserByNameLike("%國慶%");
?????????????for(User?u:userProvider.findUser(userSpec)){
??????????????????System.out.println(u);
?????????????}
?????}
}
運行結果如下所示:
===名字包含國慶的人員===
用戶名:蘇國慶 年齡:23
用戶名:國慶牛 年齡:82
用戶名:張國慶三 年齡:10
到目前為止,我們已經設計了一個可擴展的對象查詢平臺,但是我們還有遺留問題未解決,看看SQL語句,為什么where后面會很長?是因為有AND、OR、NOT這些邏輯操作符的存在,它們可以串聯起多個判斷語句,然后整體反饋出一個結果來。想想看,我們上面的平臺能支持這種邏輯操作符嗎?不能,你要說能,那也說得通,需要兩次過濾才能實現,比如要找名字包含“國慶”并且年齡大于25歲的用戶,代碼該怎么修改?如代碼清單38-14所示。
代碼清單38-14 復合查詢
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義一個規格書
?????????????IUserSpecification?userSpec1?=?new?UserByNameLike("%國慶%");
?????????????IUserSpecification?userSpec2?=?new?UserByAgeThan(20);
?????????????userList?=?userProvider.findUser(userSpec1);
?????????????for(User?u:userProvider.findUser(userSpec2)){
?????????????????????System.out.println(u);
?????????????}
?????}
}
能夠實現,但是思考一下程序邏輯,它采用了兩次過濾,也就是兩次循環,如果對象數量少還好說,如果對象數量巨大,這個效率就太低了,這是其一;其二,組合方式非常多,比如“與”、“或”、“非”可以自由組合,姓名中包含“國慶”但年齡小于25的用戶,姓名中不包含國慶但年齡大于25歲的用戶等,我們還能如此設計嗎?太多的組合方式,產生組合爆炸,這種設計就不妥了,應該有更優秀的方案。
我們換個方式思考該問題,不管是AND或者OR或者NOT操作,它們的返回結果都還是一個規格書,只是邏輯更復雜了而已,這3個操作符只是提供了對原有規格書的復合作用,換句話說,規格書對象之間可以進行與或非操作,操作的結果不變,分析到這里,我們就可以開始修改接口了,如代碼清單38-15所示。
代碼清單38-15 帶與或非的規格書接口
public?interface?IUserSpecification?{
?????//候選者是否滿足要求
?????public?boolean?isSatisfiedBy(User?user);
?????//and操作
?????public?IUserSpecification?and(IUserSpecification?spec);
?????//or操作
?????public?IUserSpecification?or(IUserSpecification?spec);
?????//not操作
?????public?IUserSpecification?not();
}
在規格書接口中增加了與或非的操作,接口修改了,實現類當然也要修改。先全面思考一下業務,與或非是不可擴展的操作,規格書(也就是規格對象)之間的操作只有這三種方法,是不需要擴展也不用預留擴展空間的。如此,我們就可以把與或非的實現放到基類中,那現在的問題變成了怎么在基類中實現與或非。注意看它們的返回值都需要返回規格書類型,很明顯,我們在這里要用到遞歸調用了。可以這樣理解,基類需要子類提供業務邏輯支持,因為基類是一個抽象類,不能實例化后返回,我們把簡單類圖畫出來,如圖38-3所示。

圖38-3 與規格的示意
基類對子類產生了依賴,然后進行遞歸計算,大家一定會發出這樣的疑問:父類怎么可能依賴子類,這還是面向接口編程嗎?想想看,我們提出面向接口編程的目的是什么?是為了適應變化,擁抱變化,對于不可能發生變化的部分為什么不能固化呢?與或非操作符號還會增加修改嗎?規格書對象之間的操作還有其他嗎?思考清楚這些問題后,答案就迎刃而解了。
注意 父類依賴子類的情景只有在非常明確不會發生變化的場景中存在,它不具備擴展性,是一種固化而不可變化的結構。
分析完畢,我們設計出詳細的類圖,如圖38-4所示。
可能大家有很多的疑問,我們先來分析代碼,代碼分析完畢估計能解決你大部分的疑問。規格書接口如代碼清單38-15所示,不再贅述。我們來看組合規格書(CompositeSpecification),它是一個抽象類,實現了與或非的操作,如代碼清單38-16所示。
代碼清單38-16 組合規格書
public?abstract?class?CompositeSpecification?implements?IUserSpecification?{
?????//是否滿足條件由實現類實現
?????public?abstract?boolean?isSatisfiedBy(User?user);
?????//and操作
?????public?IUserSpecification?and(IUserSpecification?spec)?{
?????????????return?new?AndSpecification(this,spec);
?????}
?????//not操作
?????public?IUserSpecification?not()?{
?????????????return?new?NotSpecification(this);
?????}
?????//or操作
?????public?IUserSpecification?or(IUserSpecification?spec)?{
?????????????return?new?OrSpecification(this,spec);
?????}
}

圖38-4 完整規格書類圖
候選對象是否滿足條件是由isSatisfiedBy方法決定的,它代表的是一個判斷邏輯,由各個實現類實現。三個與或非操作在抽象類中實現,它是通過直接new了一個子類,如此設計非常符合單一職責原則,每個子類都有一個獨立的職責,要么完成“與”操作,要么完成“或”操作,要么完成“非”操作。我們先來看“與”操作規格書,如代碼清單38-17所示。
代碼清單38-17 與規格書
public?class?AndSpecification?extends?CompositeSpecification?{
?????//傳遞兩個規格書進行and操作
?????private?IUserSpecification?left;
?????private?IUserSpecification?right;
?????public?AndSpecification(IUserSpecification?_left,IUserSpecification?_right){
?????????????this.left?=?_left;
?????????????this.right?=?_right;
?????}
?????//進行and運算
?????@Override
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????return?left.isSatisfiedBy(user)?&&?right.isSatisfiedBy(user);
?????}
}
通過構造函數傳遞過來兩個需要操作的規格書,然后通過isSatisfiedBy方法返回兩者and操作的結果。或規格書和非規格書與此類似,分別如代碼清單38-18、代碼清單38-19所示。
代碼清單38-18 或規格書
public?class?OrSpecification?extends?CompositeSpecification?{
?????//左右兩個規格書
?????private?IUserSpecification?left;
?????private?IUserSpecification?right;
?????public?OrSpecification(IUserSpecification?_left,IUserSpecification?_right){
?????????????this.left?=?_left;
?????????????this.right?=?_right;
?????}
?????//or運算
?????@Override
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????return?left.isSatisfiedBy(user)?||?right.isSatisfiedBy(user);
?????}
}
代碼清單38-19 非規格書
public?class?NotSpecification?extends?CompositeSpecification?{
?????//傳遞一個規格書
?????private?IUserSpecification?spec;
?????public?NotSpecification(IUserSpecification?_spec){
?????????????this.spec?=?_spec;
?????}
?????//not操作
?????@Override
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????return?!spec.isSatisfiedBy(user);
?????}
}
這三個規格書都是不發生變化的,只要使用該框架,三個規格書都要實現的,而且代碼基本上是雷同的,所以才有了父類依賴子類的設計,否則是嚴禁出現父類依賴子類的情況的。大家再仔細看看這三個規格書和組合規格書,代碼很簡單,但也很巧妙,它跳出了我們面向對象設計的思維,不變部分使用一種固化方式實現。
姓名相同、年齡大于基準年齡、Like格式等規格書都有少許改變,把實現接口變為繼承基類,我們以名字相等規格書為例,如代碼清單38-20所示。
代碼清單38-20 姓名相同規格書
public?class?UserByNameEqual?extends?CompositeSpecification?{
?????//基準姓名
?????private?String?name;
?????//構造函數傳遞基準姓名
?????public?UserByNameEqual(String?_name){
?????????????this.name?=?_name;
?????}
?????//檢驗用戶是否滿足條件
?????public?boolean?isSatisfiedBy(User?user)?{
?????????????return?user.getName().equals(name);
?????}
}
僅僅修改了黑體部分,其他沒有任何改變。另外兩個規格書修改相同,不再贅述。其他的User及UserProvider沒有任何改動,不再贅述。
我們修改一下場景類,如代碼清單38-21所示。
代碼清單38-21 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//首先初始化一批用戶
?????????????ArrayList<User>?userList?=?new?ArrayList<User>();
?????????????userList.add(new?User("蘇國慶",23));
?????????????userList.add(new?User("國慶牛",82));
?????????????userList.add(new?User("張國慶三",10));
?????????????userList.add(new?User("李四",10));
?????????????//定義一個用戶查詢類
?????????????IUserProvider?userProvider?=?new?UserProvider(userList);
?????????????//打印出名字包含"國慶"的人員
?????????????System.out.println("===名字包含國慶的人員===");
?????????????//定義一個規格書
?????????????IUserSpecification?spec?=?new?UserByAgeThan(25);
?????????????IUserSpecification?spec2?=?new?UserByNameLike("%國慶%");
?????????????for(User?u:userProvider.findUser(spec.and(spec2))){
?????????????????????System.out.println(u);
?????????????}
?????}
}
在場景類中我們建立了兩個規格書,一個是年齡大于25的用戶,另一個是名字中包含“國慶”兩個字的用戶,這兩個規格書之間的關系是“與”關系,運行結果如下:
===名字包含國慶的人員===
用戶名:國慶牛 年齡:82
到此為止我們的LINQ已經完成了很大一部分了,SQL語句中的where后面部分已經可以解析了,完全可以再增加年齡相等的規格書、姓名字數規格書等,你在SQL中使用過的條件在這里都能實現了。功臣還是依賴于三個與或非規格書,有了它們三個棟梁才能組合出一個精彩的條件查詢世界。
38.1.2 最佳實踐
我們在例子中多次提到規格兩個字,該實現模式就叫做規格模式(Specification Pattern),它不屬于23個設計模式,它是其中一個模式的擴展,是哪個模式呢?
我們用全局的觀點思考一下,基類代表的是所有的規格書,它的目的是描述一個完整的、可組合的規格書,它代表的是一個整體,其下的And規格書、Or規格書、Not規格書、年齡大于基準年齡規格書等都是一個真實的實現,也就是一個局部,現在我們又回到了整體和部分的關系了,那這是什么模式?對,組合模式,它是組合模式的一種特殊應用,我們來看它的通用類圖,如圖38-5所示。

圖38-5 規格模式通用類圖
為什么在通用類圖中把方法名稱都定義出來呢?是因為只要使用規格模式,方法名稱都是這四個,它是把組合模式更加具體化了,放在一個更狹小的應用空間中。我們再仔細看看,還能不能找到其他模式的身影?對,策略模式,每個規格書都是一個策略,它完成了一系列邏輯的封裝,用年齡相等的規格書替換年齡大于指定年齡的規格書上層邏輯有什么改變嗎?不需要任何改變!
規格模式非常重要,它巧妙地實現了對象篩選功能。我們來看其通用源碼,首先看抽象規格書,如代碼清單38-22所示。
代碼清單38-22 抽象規格書
public?interface?ISpecification?{
?????//候選者是否滿足要求
?????public?boolean?isSatisfiedBy(Object?candidate);
?????//and操作
?????public?ISpecification?and(ISpecification?spec);
?????//or操作
?????public?ISpecification?or(ISpecification?spec);
?????//not操作
?????public?ISpecification?not();
}
組合規格書實現與或非的算法,如代碼清單38-23所示。
代碼清單38-23 組合規格書
public?abstract?class?CompositeSpecification?implements?ISpecification?{
?????//是否滿足條件由實現類實現
?????public?abstract?boolean?isSatisfiedBy(Object?candidate);
?????//and操作
?????public?ISpecification?and(ISpecification?spec)?{
?????????????return?new?AndSpecification(this,spec);
?????}
?????//not操作
?????public?ISpecification?not()?{
?????????????return?new?NotSpecification(this);
?????}
?????//or操作
?????public?ISpecification?or(ISpecification?spec)?{
?????????????return?new?OrSpecification(this,spec);
?????}
}
與或非規格書代碼分別如代碼清單38-24至代碼清單38-26所示。
代碼清單38-24 與規格書
public?class?AndSpecification?extends?CompositeSpecification?{
?????//傳遞兩個規格書進行and操作
?????private?ISpecification?left;
?????private?ISpecification?right;
?????public?AndSpecification(ISpecification?_left,ISpecification?_right){
?????????????this.left?=?_left;
?????????????this.right?=?_right;
?????}
?????//進行and運算
?????@Override
?????public?boolean?isSatisfiedBy(Object?candidate)?{
?????????????return?left.isSatisfiedBy(candidate)?&&?right.isSatisfiedBy(candidate);
?????}
}
代碼清單38-25 或規格書
public?class?OrSpecification?extends?CompositeSpecification?{
?????//左右兩個規格書
?????private?ISpecification?left;
?????private?ISpecification?right;
?????public?OrSpecification(ISpecification?_left,ISpecification?_right){
?????????????this.left?=?_left;
?????????????this.right?=?_right;
?????}
?????//or運算
?????@Override
?????public?boolean?isSatisfiedBy(Object?candidate)?{
?????????????return?left.isSatisfiedBy(candidate)?||?right.isSatisfiedBy(candidate);
?????}
}
代碼清單38-26 非規格書
public?class?NotSpecification?extends?CompositeSpecification?{
?????//傳遞一個規格書
?????private?ISpecification?spec;
?????public?NotSpecification(ISpecification?_spec){
?????????????this.spec?=?_spec;
?????}
?????//not操作
?????@Override
?????public?boolean?isSatisfiedBy(Object?candidate)?{
?????????????return?!spec.isSatisfiedBy(candidate);
?????}
}
以上一個接口、一個抽象類、3個實現類只要在適用規格模式的地方都完全相同,不用做任何的修改,大家閉著眼照抄就成,要修改的是下面的規格書——業務規格書,如代碼清單38-27所示。
代碼清單38-27 業務規格書
public?class?BizSpecification?extends?CompositeSpecification?{
?????//基準對象
?????private?Object?obj;
?????public?BizSpecification(Object?_obj){
?????????????this.obj?=?_obj;
?????}
?????@Override
?????public?boolean?isSatisfiedBy(Object?candidate)?{
?????????????//根據基準對象和候選對象,進行業務判斷,返回boolean
?????????????return?false;
?????}
}
然后就是看怎么使用了,場景類如代碼清單38-28所示。
代碼清單38-28 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//待分析的對象
?????????????ArrayList<Object>?list?=?new?ArrayList<Object>();
?????????????//定義兩個業務規格書
?????????????ISpecification?spec1?=?new?BizSpecification(new?Object());
?????????????ISpecification?spec2?=?new?BizSpecification(new?Object());
?????????????//規則的調用
?????????????for(Object?obj:list){
?????????????????????if(spec1.and(spec2).isSatisfiedBy(obj)){??//and操作
??????????????????????????????System.out.println(obj);
??????????????????}
?????????????}
?????}
}
規格模式已經是一個非常具體的應用框架了(相對于23個設計模式),大家遇到類似多個對象中篩選查找,或者業務規則不適于放在任何已有實體或值對象中,而且規則的變化和組合會掩蓋那些領域對象的基本含義,或者是想自己編寫一個類似LINQ的語言工具的時候就可以照搬這部分代碼,只要實現自己的邏輯規格書即可。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——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種設計模式彩圖