命令模式

命令模式(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中對象的序列化,詳細的介紹了對象序列化的概念。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章