以前項目寫過關於TR069協議報文處理的代碼(主要是基於SOAP協議發送一些遠程命令並處理響應),在設計的時候,想的是應用策略模式對報文進行解析處理,
下圖是主要代碼結構(和策略模式很像)
代碼類似於:
/** * 1、需要解析的XML */ String xml = "<xml>...</xml>"; /** * 2、獲取xml類型 */ MessageType type = SoapMessageFactory.getRpcType(xml); /** * 3、初始化soapMessage(最好先判斷下xml類型) */ SoapMessage soap = SoapMessageFactory.initSoap(type); /** * 4、解析xml,可通過header和body變量獲取結果 */ soap.parse(xml);
但回過頭來仔細看,這哪是什么策略模式呢,明明是更符合命令模式(message對應command,paser對應receiver),策略模式和命令模式這么相似嗎,這么容易混淆?於是我就進行分析比較。
基本定義:
策略模式:定義一系列算法,把它們一個個封裝起來,並且使它們可以相互替換。該模式使得算法可獨立於它們的客戶變化。
命令模式:將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數化,對請求排隊或者記錄請求日志,可以提供命令的撤銷和恢復功能。
以SOAP報文解析為例,先從設計思路分析。
1、最初想法:將所有報文都統一看做為SoapMessage,定義不同的算法進行封裝,對不同的報文進行不同的解析。認為是策略模式。
2、進一步:其實Soap報文根據不同的type相當於不同請求(命令),定義並封裝不同的算法,是與相應的報文對應的。認為是命令模式。
第一種思路與策略模式的定義對比后就可以發現明顯是不對的,因為策略模式會要求不同算法相互替換,而不同報文解析是不能替換的。
策略模式側重同一個問題可用不同的可替換的算法去解決,而實際使用中,說的苛刻些,調用者只取其一,執行一次算法即可;比如對數組排序,用插入排序算法排好序后,你不會再用堆排序重新拍一遍,又比如你用支付寶支付后,不會再用微信支付一遍;而這些場景都可以應用策略模式。
策略模式網上的例子很多,我覺得這篇文章總結的特別好:http://www.cnblogs.com/justinw/archive/2007/02/06/641414.html
====================分割線===================================
而對於命令模式更多的解釋是這樣:不同問題使用不同算法或者側重將請求的發送者和接收者解耦,這難道真的抓住命令模式的本質了嗎?
首先看一下命令模式的UML圖:
有5個角色:Invoker(調用者)和Receiver(接收者)、command接口和ConcreteCommand實現類以及Client。
首先注明:如果對於跨進程或者跨機器的這種遠程的方法調用,使用類似命令模式的這種構造方法相信沒有人有異議,下面對命令模式的分析側重單一進程。
網上很多人都使用下面的例子進行解釋:

1 class Invoker { 2 private Command command; 3 public Invoker(Command command) { 4 this.command = command; 5 } 6 public void action(){ 7 this.command.execute(); 8 } 9 } 10 11 abstract class Command { 12 public abstract void execute(); 13 } 14 15 class ConcreteCommand extends Command { 16 private Receiver receiver; 17 public ConcreteCommand(Receiver receiver){ 18 this.receiver = receiver; 19 } 20 public void execute() { 21 this.receiver.doSomething(); 22 } 23 } 24 25 class Receiver { 26 public void doSomething(){ 27 System.out.println("接受者-業務邏輯處理"); 28 } 29 } 30 31 public class Client { 32 public static void main(String[] args){ 33 Receiver receiver = new Receiver(); 34 Command command = new ConcreteCommand(receiver); 35 Invoker invoker = new Invoker(command); 36 invoker.action(); 37 } 38 }
上面的代碼看似和命令模式的類圖很吻合,我覺得更像是為了模式而模式所拼湊出的一段代碼,沒有太多實際意義,甚至會誤導人。這段代碼最多體現了命令模式定義的前半部分:“將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數化”,而即使用來說明這一部分,代碼也有部分是多余的,那就是Receiver;main方法的執行過程更像一個蹺蹺板,Invoker翹一下Command,Command翹一下Receiver,而Receiver執不執行、什么時候執行,為什么由別的對象說了算呢?!或者直接去掉Receiver,留下Invoker,Command和ConcreteCommand,Invoker保持對Command的引用,而ConcreteCommand封裝自己的特性,命令怎么執行由ConcreteCommand的邏輯確定,而這也體現了面向接口編程原則,Command依舊可獨立於Invoker變化。根據上面的分析,我總結了命令模式的一種變形,稱之為“簡約型命令模式”。
一)簡約型命令模式
UML圖:
這里去掉了Receiver,Invoker持有對Command的引用,每個Command都有唯一的方法Execute()。在Client中加入了命令隊列(Queue為虛線,表示可有可無),Client可以一次生成一個命令,馬上執行,或者一次生成若干命令放入隊列,依次執行。
這種方式真是像極了“策略模式”,這可能就是我把命令模式和策略模式混淆的原因,而這種模式不能稱為策略模式,就是因為算法是不可相互替代的,稱之為“簡約型命令模式”。
可參考:http://www.jdon.com/designpatterns/command.htm
既然有簡約型,就對應會有較復雜的類型,當然主要還是參考定義,注重實現其后半部分:“對請求排隊或者記錄請求日志,可以提供命令的撤銷和恢復功能。”,我稱之為“異步事務性命令模式”。
二)異步事務型命令模式
當然還是先看UML圖:
結構與原始的命令模式基本一樣,唯一修改的是Receiver對象,增加了一個pool屬性,用來接收Command,Invoker每次調用命令,並不會直接執行而是先寫入pool中,待Receiver等到合適時機去選擇執行,這種結構把Invoker和Receiver徹底解耦,Receiver完全不受Invoker制約,也完全體現了一個Receiver的角色。入隊的同時,還可以將命令持久化,以一種Write ahead log的形式對其進行記錄,還有ConcreteCommand中有個state屬性,很多介紹命令模式的文章對沒有對其進行說明,我認為這是實現事務必不可少的(可以用來指示此命令與某些命令是一組的,要么都執行成功,要么異常回滾,或者可以結合Composite模式使用),Receiver中添加的undo()和redo()方法就是和事務相關的操作,如果服務異常退出,重啟后,使用redo()方法結合wal日志進行恢復,如果事務執行中斷就需要根據命令執行前的狀態進行恢復。
目前還沒有看到相關的實例。
最后還說一句:“不同問題使用不同算法或者側重將請求的發送者和接收者解耦”只是命令模式的一種表現形式,解耦、不同問題不同算法,太容易讓人和其他的模式相混淆,還是應該回到最初的定義去理解。
PS:最后寫的有點倉促,對於這種事務設計還要繼續研究