1、概述
命令模式和策略模式的類圖確實很相似,只是命令模式多了一個接收者(Receiver)角色。它們雖然同為行為類模式,但是兩者的區別還是很明顯的。策略模式的意圖是封裝算法,它認為“算法”已經是一個完整的、不可拆分的原子業務(注意這里是原子業務,而不是原子對象),即其意圖是讓這些算法獨立,並且可以相互替換,讓行為的變化獨立於擁有行為的客戶;而命令模式則是對動作的解耦,把一個動作的執行分為執行對象(接收者角色)、執行行為(命令角色),讓兩者相互獨立而不相互影響。
我們從一個相同的業務需求出發,按照命令模式和策略模式分別設計出一套實現,來看看它們的側重點有什么不同。zip和gzip文件格式相信大家都很熟悉,它們是兩種不同的壓縮格式,我們今天就來對一個目錄或文件實現兩種不同的壓縮方式:zip壓縮和gzip壓縮(這里的壓縮指的是壓縮和解壓縮兩種對應的操作行為,下同)。
2、策略模式實現壓縮算法
2.1 類圖
使用策略模式實現壓縮算法非常簡單,也是非常標准的。
在類圖中,我們的側重點是zip壓縮算法和gzip壓縮算法可以互相替換,一個文件或者目錄可以使用zip壓縮,也可以使用gzip壓縮,選擇哪種壓縮算法是由高層模塊(實際操作者)決定的。

2.2 代碼
2.2.1 抽象的壓縮算法
我們來看一下代碼實現。先看抽象的壓縮算法。
class CIAlgorithm { public: CIAlgorithm(){}; ~CIAlgorithm(){}; //壓縮算法 bool mbCompress(const string &sSource, const string &sTo); //解壓縮算法 bool mbUncompress(const string &sSource, const string &sTo); };
每一個算法要實現兩個功能:壓縮和解壓縮,傳遞進來一個絕對路徑source,compress把它壓縮到to目錄下,uncompress則進行反向操作——解壓縮,這兩個方法一定要成對地實現,為什么呢?用gzip解壓縮算法能解開zip格式的壓縮文件嗎?
2.2.2 zip壓縮算法
class CZip : public CIAlgorithm { public: CZip(){}; ~CZip(){}; //zip格式的壓縮算法 bool mbCompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP壓縮成功!" << endl; return true; } // zip格式的解壓縮算法 bool mbUncompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP解壓縮成功!" << endl; return true; } };
2.2.3 gzip壓縮算法
class CGzip : public CIAlgorithm { public: CGzip(){}; ~CGzip(){}; //gzip的壓縮算法 bool mbCompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP壓縮成功!" << endl; return true; } // gzip解壓縮算法 bool mbUncompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP解壓縮成功!" << endl; return true; } };
2.2.4 環境角色
這兩種壓縮算法實現起來都很簡單,兩個具體的算法實現了同一個接口,完全遵循依賴倒轉原則。我們再來看環境角色。
class CContext { public: //構造函數傳遞具體的算法 CContext(CIAlgorithm *opAlgorithm){ mopAlgotithm = opAlgorithm; }; ~CContext(){}; // 執行壓縮算法 bool mbCompress(const string &sSource, const string &sTo) { this->mopAlgotithm->mbCompress(sSource, sTo); return true; } //執行解壓縮算法 bool mbUncompress(const string &sSource, const string &sTo) { this->mopAlgotithm->mbUncompress(sSource, sTo); return true; } private: //指向抽象算法 CIAlgorithm *mopAlgotithm; };
也是非常簡單,指定一個算法,執行該算法,一個標准的策略模式就編寫完畢了。
2.2.5 調用
請注意,這里雖然有兩個算法Zip和Gzip,但是對調用者來說,這兩個算法沒有本質上的區別,只是“形式”上不同,什么意思呢?從調用者來看,使用哪一個算法都無所謂,兩者完全可以互換,甚至用一個算法替代另外一個算法。
int main() { //對文件執行zip壓縮算法 CContext *op_context = new CContext(new CZip); //執行壓縮算法 op_context->mbCompress("c:\\windows", "d:\\windows.zip"); //執行解壓縮算法 op_context->mbUncompress("c:\\windows.zip", "d:\\windows"); return 0; }
2.2.6 執行結果

要使用gzip算法嗎?在用戶調用換成new CGzip可以了,其他的模塊根本不受任何影響,策略模式關心的是算法是否可以相互替換。策略模式雖然簡單,但是在項目組使用得非常多,可以說隨手拈來就是一個策略模式。
3、命令模式實現壓縮算法
3.1 類圖
命令模式的主旨是封裝命令,使請求者與實現者解耦。例如,到飯店點菜,客人(請求者)通過服務員(調用者)向廚師(接收者)發送了訂單(行為的請求),該例子就是通過封裝命令來使請求者和接收者解耦。我們繼續來看壓縮和解壓縮的例子,怎么使用命令模式來完成該需求呢?我們先畫出類圖。
類圖看着復雜,但是還是一個典型的命令模式,通過定義具體命令完成文件的壓縮、解壓縮任務,注意我們這里對文件的每一個操作都是封裝好的命令,對於給定的請求,命令不同,處理的結果當然也不同,這就是命令模式要強調的。

3.2 代碼
3.2.1 抽象命令
class CCMd { public: virtual bool mbExecute(const string &sSource, const string &sTo) = 0; protected: CCMd() { mopZip = new CZipReceiver; mopGzip = new CGzipReceiver; } ~CCMd(){}; protected: CIReceiver *mopZip; CIReceiver *mopGzip; };
抽象命令定義了兩個接收者的引用:zip接收者和gzip接收者,它們完全是受眾,人家讓它干啥它就干啥,具體使用哪個接收者是命令決定的。具體命令有4個:zip壓縮、zip解壓縮、gzip壓縮、gzip解壓縮。
3.2.2 zip壓縮命令
class CZipCompressCmd : public CCMd { public: CZipCompressCmd(){}; ~CZipCompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopZip->mbCompress(sSource, sTo); } };
3.2.3 zip解壓縮命令
class CZipUncompressCmd : public CCMd { public: CZipUncompressCmd(){}; ~CZipUncompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopZip->mbUncompress(sSource, sTo); } };
3.2.4 gzip壓縮命令
class CGzipCompressCmd : public CCMd { public: CGzipCompressCmd(){}; ~CGzipCompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopGzip->mbCompress(sSource, sTo); } };
3.2.5 gzip解壓縮命令
class CGzipUncompressCmd : public CCMd { public: CGzipUncompressCmd(){}; ~CGzipUncompressCmd(){}; bool mbExecute(const string &sSource, const string &sTo) { return mopGzip->mbUncompress(sSource, sTo); } };
它們非常簡單,都只有一個方法,堅決地執行命令,使用了委托的方式,由接收者來實現。
3.2.6 抽象接收者
class CIReceiver { public: CIReceiver(){}; ~CIReceiver(){}; //壓縮 virtual bool mbCompress(const string &sSource, const string &sTo) = 0; //解壓縮 virtual bool mbUncompress(const string &sSource, const string &sTo) = 0; };
抽象接收者與策略模式的抽象策略完全相同,具體的實現也完全相同,只是類名做了改動。
3.2.7 zip接收者
class CZipReceiver :public CIReceiver { public: CZipReceiver(){}; ~CZipReceiver(){}; //zip格式的壓縮算法 bool mbCompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP壓縮成功!" << endl; return true; } //zip格式的解壓縮算法 bool mbUncompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",ZIP解壓縮成功!" << endl; return true; } };
這就是一個具體動作執行者,它在策略模式中是一個具體的算法,關心的是是否可以被替換;而在命令模式中,它則是一個具體、真實的命令執行者。
3.2.8 gzip接收者
class CGzipReceiver :public CIReceiver { public: CGzipReceiver(){}; ~CGzipReceiver(){}; //zip格式的壓縮算法 bool mbCompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP壓縮成功!" << endl; return true; } //zip格式的解壓縮算法 bool mbUncompress(const string &sSource, const string &sTo) { cout << sSource.c_str() << "-->" << sTo.c_str() << ",GZIP解壓縮成功!" << endl; return true; } };
3.2.9 調用者
命令、 接收者都具備了, 我們再來封裝一個命令的調用者。
class CInvoker { public: CInvoker(CCMd *opCmd) { mopCmd = opCmd; } ~CInvoker(){}; //執行命令 bool mbExecute(const string &sSource, const string &sTo) { mopCmd->mbExecute(sSource, sTo); return true; } private: //抽象命令的引用 CCMd *mopCmd; };
調用者非常簡單,只負責把命令向后傳遞,當然這里也可以進行一定的攔截處理,我們暫時用不到就不做處理了。
3.2.10 場景調用
int main() { //定義一個命令,壓縮一個文件 CCMd *op_cmd = new CZipCompressCmd; //定義調用者 CInvoker *op_invoker = new CInvoker(op_cmd); cout << "========執行壓縮命令========" << endl; op_invoker->mbExecute("c:\\windows", "d:\\windows.zip"); return 0; }
3.2.11 執行結果

3.3 小結
想新增一個命令?當然沒有問題,只要重新定義一個命令就成,命令改變了,高層模塊只要調用它就成。請注意,這里的程序還有點欠缺,沒有與文件的后綴名綁定,不應該出現使用zip壓縮命令產生一個.gzip后綴的文件名,讀者在實際應用中可以考慮與文件后綴名之間建立關聯。
通過以上例子,我們看到命令模式也實現了文件的壓縮、解壓縮的功能,它的實現是關注了命令的封裝,是請求者與執行者徹底分開,看看我們的程序,執行者根本就不用了解命令的具體執行者,它只要封裝一個命令——“給我用zip格式壓縮這個文件”就可以了,具體由誰來執行,則由調用者負責,如此設計后,就可以保證請求者和執行者之間可以相互獨立,各自發展而不相互影響。
同時,由於是一個命令模式,接收者的處理可以進行排隊處理,在排隊處理的過程中,可以進行撤銷處理,比如客人點了一個菜,廚師還沒來得及做,那要撤回很簡單,撤回也是命令,這是策略模式所不能實現的。
4、總結
命令模式和策略模式的類圖完全一樣,代碼實現也比較類似,但是兩者還是有區別的。
● 關注點不同
策略模式關注的是算法替換的問題,一個新的算法投產,舊算法退休,或者提供多種算法由調用者自己選擇使用,算法的自由更替是它實現的要點。換句話說,策略模式關注的是算法的完整性、封裝性,只有具備了這兩個條件才能保證其可以自由切換。
命令模式則關注的是解耦問題,如何讓請求者和執行者解耦是它需要首先解決的,解耦的要求就是把請求的內容封裝為一個一個的命令,由接收者執行。由於封裝成了命令,就同時可以對命令進行多種處理,例如撤銷、記錄等。
  ● 角色功能不同
   在我們的例子中,策略模式中的抽象算法和具體算法與命令模式的接收者非常相似,但是它們的職責不同。策略模式中的具體算法是負責一個完整算法邏輯,它是不可再拆分的原子業務單元,一旦變更就是對算法整體的變更。
而命令模式則不同,它關注命令的實現,也就是功能的實現。例如我們在分支中也提到接收者的變更問題,它只影響到命令族的變更,對請求者沒有任何影響,從這方面來說,接收者對命令負責,而與請求者無關。命令模式中的接收者只要符合六大設計原則,完全不用關心它是否完成了一個具體邏輯,它的影響范圍也僅僅是抽象命令和具體命令,對它的修改不會擴散到模式外的模塊。
當然,如果在命令模式中需要指定接收者,則需要考慮接收者的變化和封裝,例如一個老顧客每次吃飯都點同一個廚師的飯菜,那就必須考慮接收者的抽象化問題。
  ● 使用場景不同
   策略模式適用於算法要求變換的場景,而命令模式適用於解耦兩個有緊耦合關系的對象場合或者多命令多撤銷的場景。
