命令模式(Command)結構並不複雜,簡單的說,就是將某些職責封裝成對象,對象的激發者(Invoker)和接收者(Reciever)都不過問命令的執行過程。用這樣的方式解除了激發者和接受者之間的耦合,為程式提供了更清晰的階層。實際運用中經常是將系統中的命令抽象成一個介面,所有的命令都是這個介面的實現,如下所示:
Command類是ICommand的實現,Command類實現了execute方法,其他命令類都是Command方法的子類。這樣激發者接受者和接受者面臨的環境就非常簡單了:統一作為Command類型就可以處理了。
最簡單的命令模式的實現代碼如下:
///定義Command介面 public interface ICommand { void execute(); } ///實現ICommand介面 ///Command類是所有命令的基類,在這裡實現一些命令的共同處理過程 public abstract class Command : ICommand { public virtual void execute() { } //實現其他的方法.... } ///實現一個具體的Command1 public class Command1 : Command { public void override execute() { //具體的處理過程 } } ///實現一個具體的Command2 public class Command2 : Command { public void override execute() { //具體的處理過程 } } ///實現一個具體的Command3 public class Command3 : Command { public void override execute() { //具體的處理過程 } } |
命令模式的實際應用
下面從一個系統執行個體看一看命令模式的應用。ReportingService是一個報表系統的一部分,是一個運行在Windows系統上的Service程式,所需要完成的功能是:每天晚上11點到淩晨2點,將資料庫中的大批量的資料產生數百張報表。報表的分布較為複雜,有的要求在Web伺服器上發布報表,有的要求將報表資料輸出成一個文字檔,還有的要將報表儲存在資料庫系統中。
系統的設計運用了命令模式,其中的一部分結構如下:
程式中定義了ICommand介面,實現了多個Command類(這裡只畫出3個表示樣本),分別執行各個報表的產生任務。Command類提供了下列介面:
| 分類 |
名稱 |
類型 |
說明 |
| 屬性 |
finished |
bool |
表示任務是否已經進行完畢 |
| 屬性 |
context |
IApplicationContext |
任務執行需要的環境和資源,包括資料庫連接、檔案系統等等 |
| 方法 |
execute |
void |
執行任務 |
Service的主線程每天定時將系統運行環境進行初始化,建立一個ApplicationContext類的執行個體(Application是一個Singlton模式的類,圖中沒有體現),這個類中儲存了Command運行需要使用的全部資源。然後初始化一個Command對象列表,逐個執行每一個Command。這樣一來,複雜的商務邏輯就從程式的主架構中分離開了,Service的主線程所需要做的全部工作就是協調資源的分配和異常的處理,還可以不斷檢查命令執行的狀態,看看finished屬性是否為真。如果執行完以後發現某些命令執行不成功,要做相應的處理。
程式採用了單元測試的開發方式,在對程式主線程進行測試的時候,使用了一個虛擬Command實現。測試每一個Command類的時候則為每個Command實現了虛擬ApplicationContext。這樣的結構為單元測試提供了很多便利。關於提高程式的可測試性可以參見我的另一篇文章:怎樣測試代碼中難測試的部分。
命令模式和其他模式的結合
為了給Command的建立提供一個整潔簡單的方式,Command模式經常和原廠模式(Factory)結合使用,採用一個工廠對Command對象的建立進行管理,減少命令激發者與命令對象之間的耦合程度。
在表單程式中,Command模式還經常與組合模式(Composition)結合使用。可以在初始化表單的時候,將畫面上的功能表項目、工具列、按鈕等控制項與相應的Command進行組合,這樣在控制項點擊時,調用對應的Command,即可實現對命令的處理。
在《程式員》雜誌本年第10月期介紹了一個利用Command模式實現程式中Undo功能的文章。程式中為每個Command定義了“執行”和“撤銷”兩種操作,如下:
圖中的Action就是Commamnd對象,只是名稱不同,意義是一樣的。Action對象存在do和undo方法,分別是“執行”和“撤銷”方法。可以將執行完畢的Action對象的done屬性設定為true,再將其儲存在一個列表中——ActionList。在ActionList中找到最後一個done屬性為true的Action,執行其undo方法,即可實現“撤銷”的操作。
命令類的序列化
如果將Command基類及其每一個子類都進行序列化,可以實現更多的強大的功能。程式執行過程中,如果發生異常,比如網路發生中斷、檔案丟失等異常,可以將沒有執行完畢的Command序列化儲存下來。等待可以執行的時候再將這些對象重新載入,命令可以繼續執行。甚至使用者已經重新啟動了電腦,命令也可以繼續執行。Command對象的序列化也為分布式的操作提供了便利。在網路環境中,可以在某處建立一個Command對象,將其序列化後傳輸到網路上另一台電腦上進行執行。這種情況下不要忘記異常類的序列化,如果自己定義了Exception的子類,也要將其進行序列化,以便在網路環境中捕捉和處理Command執行時發生的異常。Microsoft網站上有一篇文章介紹.NET中對象的序列化,詳細的介紹了對象序列化的概念。
參考文獻
《實用J2EE設計模式編程指南》 (美)Graig A.Berry John Carnell Matjaz B.Juric 邱仲潘