命令模式與策略模式之己見


 

      以前項目寫過關於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 }
View Code

      上面的代碼看似和命令模式的類圖很吻合,我覺得更像是為了模式而模式所拼湊出的一段代碼,沒有太多實際意義,甚至會誤導人。這段代碼最多體現了命令模式定義的前半部分:“將一個請求封裝成一個對象,從而讓你使用不同的請求把客戶端參數化”,而即使用來說明這一部分,代碼也有部分是多余的,那就是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:最后寫的有點倉促,對於這種事務設計還要繼續研究


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM