32.1 命令模式VS策略模式
命令模式和策略模式的類圖確實很相似,只是命令模式多了一個接收者(Receiver)角色。它們雖然同為行為類模式,但是兩者的區別還是很明顯的。策略模式的意圖是封裝算法,它認為“算法”已經是一個完整的、不可拆分的原子業務(注意這里是原子業務,而不是原子對象),即其意圖是讓這些算法獨立,并且可以相互替換,讓行為的變化獨立于擁有行為的客戶;而命令模式則是對動作的解耦,把一個動作的執行分為執行對象(接收者角色)、執行行為(命令角色),讓兩者相互獨立而不相互影響。
我們從一個相同的業務需求出發,按照命令模式和策略模式分別設計出一套實現,來看看它們的側重點有什么不同。zip和gzip文件格式相信大家都很熟悉,它們是兩種不同的壓縮格式,我們今天就來對一個目錄或文件實現兩種不同的壓縮方式:zip壓縮和gzip壓縮(這里的壓縮指的是壓縮和解壓縮兩種對應的操作行為,下同)。實現這兩種壓縮格式有什么意義呢?有意義!一是zip格式(.zip后綴)是Windows操作系統常用的壓縮格式,gzip格式(.gz后綴)是*nix系統常用的壓縮格式;二是JDK提供了對zip和gzip文件的操作包,非常容易實現文件的壓縮和解壓縮操作。
下面我們來實現不同格式的壓縮和解壓縮功能。
32.1.1 策略模式實現壓縮算法
使用策略模式實現壓縮算法非常簡單,也是非常標準的,類圖如圖32-1所示。
在類圖中,我們的側重點是zip壓縮算法和gzip壓縮算法可以互相替換,一個文件或者目錄可以使用zip壓縮,也可以使用gzip壓縮,選擇哪種壓縮算法是由高層模塊(實際操作者)決定的。我們來看一下代碼實現。先看抽象的壓縮算法,如代碼清單32-1所示。

圖32-1 策略模式實現壓縮算法的類圖
代碼清單32-1 抽象壓縮算法
public?interface?Algorithm?{
?????//壓縮算法
?????public?boolean?compress(String?source,String?to);
?????//解壓縮算法
?????public?boolean?uncompress(String?source,String?to);
}
每一個算法要實現兩個功能:壓縮和解壓縮,傳遞進來一個絕對路徑source,compress把它壓縮到to目錄下,uncompress則進行反向操作——解壓縮,這兩個方法一定要成對地實現,為什么呢?用gzip解壓縮算法能解開zip格式的壓縮文件嗎?我們分別來看兩種不同格式的壓縮算法,zip、gzip壓縮算法分別如代碼清單32-2、代碼清單32-3所示。
代碼清單32-2 zip壓縮算法
public?class?Zip?implements?Algorithm?{
?????//zip格式的壓縮算法
?????public?boolean?compress(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?ZIP壓縮成功!");
?????????????return?true;
?????}
?????//zip格式的解壓縮算法
?????public?boolean?uncompress(String?source,String?to){
?????????????System.out.println(source?+?"?-->?"?+to?+?"?ZIP解壓縮成功!");
?????????????return?true;
?????}
}
代碼清單32-3 gzip壓縮算法
public?class?Gzip?implements?Algorithm?{
?????//gzip的壓縮算法
?????public?boolean?compress(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?GZIP壓縮成功!");
?????????????return?true;
?????}
?????//gzip解壓縮算法
?????public?boolean?uncompress(String?source,String?to){
?????????????System.out.println(source?+?"?-->?"?+to?+?"?GZIP解壓縮成功!");
?????????????return?true;
?????}
}
這兩種壓縮算法實現起來都很簡單,Java對此都提供了相關的API操作,這里就不再提供詳細的編寫代碼,讀者可以參考JDK自己進行實現,或者上網搜索一下,網上有太多類似的源代碼。
兩個具體的算法實現了同一個接口,完全遵循依賴倒轉原則。我們再來看環境角色,如代碼清單32-4所示。
代碼清單32-4 環境角色
public?class?Context?{
?????//指向抽象算法
?????private?Algorithm?al;
?????//構造函數傳遞具體的算法
?????public?Context(Algorithm?_al){
?????????????this.al?=?_al;
?????}
?????//執行壓縮算法
?????public?boolean?compress(String?source,String?to){
?????????????return?al.compress(source,?to);
?????}
?????//執行解壓縮算法
?????public?boolean?uncompress(String?source,String?to){
?????????????return?al.uncompress(source,?to);
?????}
}
也是非常簡單,指定一個算法,執行該算法,一個標準的策略模式就編寫完畢了。請讀者注意,這里雖然有兩個算法Zip和Gzip,但是對調用者來說,這兩個算法沒有本質上的區別,只是“形式”上不同,什么意思呢?從調用者來看,使用哪一個算法都無所謂,兩者完全可以互換,甚至用一個算法替代另外一個算法。我們繼續看調用者是如何調用的,如代碼清單32-5所示。
代碼清單32-5 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義環境角色
?????????????Context?context;
?????????????//對文件執行zip壓縮算法
?????????????System.out.println("========執行算法========");
?????????????context?=?new?Context(new?Zip());
?????????????/*
??????????????*算法替換
??????????????*?context?=?new?Context(new?Gzip());
??????????????*?
??????????????*/
?????????????//執行壓縮算法
?????????????context.compress("c:\\windows","d:\\windows.zip");
?????????????//執行解壓縮算法
?????????????context.uncompress("c:\\windows.zip","d:\\windows");
?????}
}
運行結果如下所示:
========執行算法========
c:\windows --> d:\windows.zip ZIP 壓縮成功!
c:\windows.zip --> d:\windows ZIP 解壓縮成功!
要使用gzip算法嗎?在客戶端(Client)上把注釋刪掉就可以了,其他的模塊根本不受任何影響,策略模式關心的是算法是否可以相互替換。策略模式雖然簡單,但是在項目組使用得非常多,可以說隨手拈來就是一個策略模式。
32.1.2 命令模式實現壓縮算法
命令模式的主旨是封裝命令,使請求者與實現者解耦。例如,到飯店點菜,客人(請求者)通過服務員(調用者)向廚師(接收者)發送了訂單(行為的請求),該例子就是通過封裝命令來使請求者和接收者解耦。我們繼續來看壓縮和解壓縮的例子,怎么使用命令模式來完成該需求呢?我們先畫出類圖,如圖32-2所示。
類圖看著復雜,但是還是一個典型的命令模式,通過定義具體命令完成文件的壓縮、解壓縮任務,注意我們這里對文件的每一個操作都是封裝好的命令,對于給定的請求,命令不同,處理的結果當然也不同,這就是命令模式要強調的。我們先來看抽象命令,如代碼清單32-6所示。
代碼清單32-6 抽象壓縮命令
public?abstract?class?AbstractCmd?{
?????//對接收者的引用
?????protected?IReceiver?zip?=?new?ZipReceiver();
?????protected?IReceiver?gzip?=?new?GzipReceiver();
?????//抽象方法,命令的具體單元
?????public?abstract?boolean?execute(String?source,String?to);
}

圖32-2 命令模式實現壓縮算法的類圖
抽象命令定義了兩個接收者的引用:zip接收者和gzip接收者,大家可以想象一下這兩個“受氣包”,它們完全是受眾,人家讓它干啥它就干啥,具體使用哪個接收者是命令決定的。具體命令有4個:zip壓縮、zip解壓縮、gzip壓縮、gzip解壓縮,分別如代碼清單32-7、32-8、32-9、32-10所示。
代碼清單32-7 zip壓縮命令
public?class?ZipCompressCmd?extends?AbstractCmd?{
?????public?boolean?execute(String?source,String?to)?{
?????????????return?super.zip.compress(source,?to);
?????}
}
代碼清單32-8 zip解壓縮命令
public?class?ZipUncompressCmd?extends?AbstractCmd?{
?????public?boolean?execute(String?source,String?to)?{
?????????????return?super.zip.uncompress(source,?to);
?????}
}
代碼清單32-9 gzip壓縮命令
public?class?GzipCompressCmd?extends?AbstractCmd?{
?????public?boolean?execute(String?source,String?to)?{
?????????????return?super.gzip.compress(source,?to);
?????}
}
代碼清單32-10 gzip解壓縮命令
public?class?GzipUncompressCmd?extends?AbstractCmd?{
?????public?boolean?execute(String?source,String?to)?{
?????????????return?super.gzip.uncompress(source,?to);
?????}
}
它們非常簡單,都只有一個方法,堅決地執行命令,使用了委托的方式,由接收者來實現。我們再來看抽象接收者,如代碼清單32-11所示。
代碼清單32-11 抽象接收者
public?interface?IReceiver?{
?????//壓縮
?????public?boolean?compress(String?source,String?to);
?????//解壓縮
?????public?boolean?uncompress(String?source,String?to);
}
抽象接收者與策略模式的抽象策略完全相同,具體的實現也完全相同,只是類名做了改動,我們先來看zip壓縮的實現,如代碼清單32-12所示。
代碼清單32-12 zip接收者
public?class?ZipReceiver?implements?IReceiver?{
?????//zip格式的壓縮算法
?????public?boolean?compress(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?ZIP壓縮成功!");
?????????????return?true;
?????}
?????//zip格式的解壓縮算法
?????public?boolean?uncompress(String?source,String?to){
?????????????System.out.println(source?+?"?-->?"?+to?+?"?ZIP解壓縮成功!");
?????????????return?true;
?????}
}
這就是一個具體動作執行者,它在策略模式中是一個具體的算法,關心的是是否可以被替換;而在命令模式中,它則是一個具體、真實的命令執行者。我們再來看gzip接收者,如代碼清單32-13所示。
代碼清單32-13 gzip接收者
public?class?GzipReceiver?implements?IReceiver?{
?????//gzip的壓縮算法
?????public?boolean?compress(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?GZIP壓縮成功!");
?????????????return?true;??????????
?????}
?????//gzip解壓縮算法
?????public?boolean?uncompress(String?source,String?to){
?????????????System.out.println(source?+?"?-->?"?+to?+?"?GZIP解壓縮成功!");
?????????????return?true;
?????}
}
大家可以這樣思考這個問題,接收者就是廚房的廚師,具體要哪個廚師做這道菜則是餐館的規章制度已經明確的,你讓專做粵菜的師傅做一個剁椒魚頭,能做出好菜嗎?在命令模式中,就是在抽象命令中定義了接收者的引用,然后在具體的實現類中確定要讓哪個接收者進行處理。這就好比是客人點菜:我要一個剁椒魚頭,這就是一個命令,然后服務員(Inovker)接收到這個命令后,就開始執行,把這個命令指定給具體的執行者執行。
當然了,接收者這部分還可以這樣設計,即按照職責設計接收者,比如壓縮接收者、解壓縮接收者,但接口需要稍稍改動,如代碼清單32-14所示。
代碼清單32-14 依照職責設計的接收者接口
public?interface?IReceiver?{
?????//執行zip命令
?????public?boolean?zipExec(String?source,String?to);
?????//執行gzip命令
?????public?boolean?gzipExec(String?source,String?to);
}
接收者接口只是定義了每個接收者都必須完成zip和gzip相關的兩個邏輯,有多少個職責就有多少個實現類。我們這里只有兩個職責:壓縮和解壓縮,分別如代碼清單32-15、32-16所示。
代碼清單32-15 壓縮接收者
public?class?CompressReceiver?implements?IReceiver?{
?????//執行gzip壓縮命令
?????public?boolean?gzipExec(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?GZIP壓縮成功!");
?????????????return?true;
?????}
?????//執行zip壓縮命令
?????public?boolean?zipExec(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?ZIP壓縮成功!");
?????????????return?true;
?????}
}
代碼清單32-16 解壓縮接收者
public?class?UncompressReceiver?implements?IReceiver?{
?????//執行gzip解壓縮命令
?????public?boolean?gzipExec(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?GZIP解壓縮成功!");
?????????????return?true;
?????}
?????//執行zip解壓縮命令
?????public?boolean?zipExec(String?source,?String?to)?{
?????????????System.out.println(source?+?"?-->?"?+to?+?"?ZIP解壓縮成功!");
?????????????return?true;
?????}
}
剩下的工作就是對抽象命令、具體命令稍作修改,這里不再贅述。為什么要在這里增加一個分支描述呢?這是為了與策略模式對比,在命令模式中,我們可以把接收者設計得與策略模式的算法相同,也可以不相同。我們按照職責設計的接口就不適用于策略模式,不可能封裝一個叫做壓縮的算法類,然后在類中提供兩種不同格式的壓縮功能,這違背了策略模式的意圖——封裝算法,為什么呢?如果要增加一個rar壓縮算法,該怎么辦呢?修改抽象算法?這是絕對不允許的!那為什么命令模式就是允許的呢?因為命令模式著重于請求者和接收者解耦,你管我接收者怎么變化,只要不影響請求者就成,這才是命令模式的意圖。
命令、接收者都具備了,我們再來封裝一個命令的調用者,如代碼清單32-17所示。
代碼清單32-17 調用者
public?class?Invoker?{
?????//抽象命令的引用
?????private?AbstractCmd?cmd;
?????public?Invoker(AbstractCmd?_cmd){
?????????????this.cmd?=?_cmd;
?????}
?????//執行命令
?????public?boolean?execute(String?source,String?to){
?????????????return?cmd.execute(source,?to);
?????}
}
調用者非常簡單,只負責把命令向后傳遞,當然這里也可以進行一定的攔截處理,我們暫時用不到就不做處理了。我們來看場景類是如何描述這個場景的,如代碼清單32-18所示。
代碼清單32-18 場景類
public?class?Client?{
?????public?static?void?main(String[]?args)?{
?????????????//定義一個命令,壓縮一個文件
?????????????AbstractCmd?cmd?=?new?ZipCompressCmd();
?????????????/*
??????????????*?想換一個?執行解壓命令
??????????????*?AbstractCmd?cmd?=?new?ZipUncompressCmd();
??????????????*/
?????????????//定義調用者
?????????????Invoker?invoker?=?new?Invoker(cmd);
?????????????//我命令你對這個文件進行壓縮
?????????????System.out.println("========執行壓縮命令========");
?????????????invoker.execute("c:\\windows",?"d:\\windows.zip");
?????}
}
想新增一個命令?當然沒有問題,只要重新定義一個命令就成,命令改變了,高層模塊只要調用它就成。請注意,這里的程序還有點欠缺,沒有與文件的后綴名綁定,不應該出現使用zip壓縮命令產生一個.gzip后綴的文件名,讀者在實際應用中可以考慮與文件后綴名之間建立關聯。
通過以上例子,我們看到命令模式也實現了文件的壓縮、解壓縮的功能,它的實現是關注了命令的封裝,是請求者與執行者徹底分開,看看我們的程序,執行者根本就不用了解命令的具體執行者,它只要封裝一個命令——“給我用zip格式壓縮這個文件”就可以了,具體由誰來執行,則由調用者負責,如此設計后,就可以保證請求者和執行者之間可以相互獨立,各自發展而不相互影響。
同時,由于是一個命令模式,接收者的處理可以進行排隊處理,在排隊處理的過程中,可以進行撤銷處理,比如客人點了一個菜,廚師還沒來得及做,那要撤回很簡單,撤回也是命令,這是策略模式所不能實現的。
32.1.3 小結
策略模式和命令模式相似,特別是命令模式退化時,比如無接收者(接收者非常簡單或者接收者是一個Java的基礎操作,無需專門編寫一個接收者),在這種情況下,命令模式和策略模式的類圖完全一樣,代碼實現也比較類似,但是兩者還是有區別的。
● 關注點不同
策略模式關注的是算法替換的問題,一個新的算法投產,舊算法退休,或者提供多種算法由調用者自己選擇使用,算法的自由更替是它實現的要點。換句話說,策略模式關注的是算法的完整性、封裝性,只有具備了這兩個條件才能保證其可以自由切換。
命令模式則關注的是解耦問題,如何讓請求者和執行者解耦是它需要首先解決的,解耦的要求就是把請求的內容封裝為一個一個的命令,由接收者執行。由于封裝成了命令,就同時可以對命令進行多種處理,例如撤銷、記錄等。
● 角色功能不同
在我們的例子中,策略模式中的抽象算法和具體算法與命令模式的接收者非常相似,但是它們的職責不同。策略模式中的具體算法是負責一個完整算法邏輯,它是不可再拆分的原子業務單元,一旦變更就是對算法整體的變更。
而命令模式則不同,它關注命令的實現,也就是功能的實現。例如我們在分支中也提到接收者的變更問題,它只影響到命令族的變更,對請求者沒有任何影響,從這方面來說,接收者對命令負責,而與請求者無關。命令模式中的接收者只要符合六大設計原則,完全不用關心它是否完成了一個具體邏輯,它的影響范圍也僅僅是抽象命令和具體命令,對它的修改不會擴散到模式外的模塊。
當然,如果在命令模式中需要指定接收者,則需要考慮接收者的變化和封裝,例如一個老顧客每次吃飯都點同一個廚師的飯菜,那就必須考慮接收者的抽象化問題。
● 使用場景不同
策略模式適用于算法要求變換的場景,而命令模式適用于解耦兩個有緊耦合關系的對象場合或者多命令多撤銷的場景。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——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種設計模式彩圖