標籤:
這篇部落格我們來介紹一下命令模式(Command Pattern),它是行為型設計模式之一。命令模式相對於其他的設計模式更為靈活多變,我們接觸比較多的命令模式個例無非就是程式功能表命令,如在作業系統中,我們點擊關機命令,系統就會執行一系列的操作,如先是暫停處理事件,儲存系統的一些配置,然後結束程式進程,最後調用核心命令關閉電腦等,對於這一系列的命令,使用者不用去管,使用者只需點擊系統的關機按鈕即可完成如上一系列的命令。而我們的命令模式其實也與之相同,將一系列的方法調用封裝,使用者只需調用一個方法執行,那麼所有的這些被封裝的方法就會被挨個執行調用。
轉載請註明出處:http://blog.csdn.net/self_study/article/details/52091539。
PS:對技術感興趣的同鞋加群544645972一起交流。
設計模式總目錄
java/android 設計模式學習筆記目錄
特點
將一個請求封裝成一個對象,從而讓使用者使用不同的請求把用戶端參數化;對請求排隊或者記錄請求日誌,以及支援可撤銷的操作。
命令模式的使用情境:
- 需要抽象出待執行的動作,然後以參數的形式提供出來—類似於過程設計中的回調機制,而命令模式正是回調機制的一個物件導向的替代品;
- 在不同的時刻指定、排列和執行請求,一個命令對象可以有與初始請求無關的生存期;
- 需要支援取消操作;
- 支援修改日誌功能,這樣當系統崩潰時,這些修改可以被重做一遍;
- 需要支援事務操作;
- 系統需要將一組操作組合在一起,即支援宏命令。
wiki 上列出的具體使用情境:
- GUI buttons and menu items
在 Java Swing 和 Delphi 語言中,一個 Action 是一個命令,除了執行預定的命令之外,一個 Action 可能會有一個相關聯的表徵圖,鍵盤快速鍵,氣泡提示文本等等。一個工具列按鈕或者菜單的元素可能完全用一個 Action 對象進行初始化。
- Macro recording
如果所有的使用者動作都代表了一個個命令對象,那麼一個程式就能夠輕易的儲存一系列的命令對象,之後能夠將這一系列的命令重新執行一遍來實現一個回播的動作。如果程式中嵌入了指令碼引擎,那麼每個命令對象都能夠實現 toScript() 方法,使用者的動作也能夠被輕鬆的儲存為指令碼對象。
- Mobile Code
類似於 Java 這種能夠通過 URLClassloders 將代碼變成流從一個地方傳輸到另一個地方的語言,並且程式碼程式庫中的命令使新的行為能夠被傳遞到遠程位置(EJB Command,Master Worker模式)。
- Multi-level undo
如果程式中所有的使用者動作都被實現成了命令對象,程式就能夠儲存最近被執行的命令對象,當使用者想要撤銷某些命令時,程式就可以簡單的 pop 出最近的命令並且執行他的 undo 方法。
- Networking
能夠將所有的命令對象通過網路傳輸到另外一台裝置上去執行,比如端遊中的使用者操作等。
- Parallel Processing
所有的命令都被寫成了共用資源中的任務並且並發的被很多線程同時執行(在遠程機器上這種變體可能這個會被稱為 Master/Worker 模式)。
- Progress bars
我們假定程式有一系列需要按順序執行的命令,如果每個命令對象都有一個 getEstimatedDuration() 方法,那麼程式就可以輕鬆估算出整體的執行時間,然後展示一個有意義的進度條來反應當前所有任務的執行程度。
- Thread pools
一個具有代表性的線程池可能會有一個 public 的 addTask() 方法用來添加一個工作任務到內部的等待隊列中,這個線程池中會有一系列的線程用來執行隊列中的一系列命令對象。普遍的,這些對象會實現一個通用的介面,比如 Runnable 等,用來允許線程池執行這些命令,雖然這個線程池類並不瞭解它會被用來處理具體的什麼任務。
- Transactional behavior
類似於 undo 操作,一個資料庫引擎或者軟體安裝器可能會維護一個已經執行或者將要執行的巨集指令清單,如果其中的一個失敗了,所有其他的都會被還原或者拋棄(通常被稱為 rollback,復原)。舉個例子,如果相關聯的兩個資料庫表必須要更新,並且第二個更新失敗,這個事務將會被復原,所以第一個表的更新也會被拋棄。
- Wizards
嚮導頁是使用者在點擊最後一頁”結束”按鈕時候彈出來的幾頁配置頁(配置使用者的使用習慣等),在這種情況下,一個通常分離使用者作業碼和程式碼的方法就是使用命令對象實現嚮導功能。這個命令對象會在嚮導頁第一次展示的時候被建立,每個嚮導頁將它們自己的 GUI 變化儲存在一個命令對象中,所以這個對象被定位為使用者的進一步操作。“結束”動作簡單的觸發了一個 excete() 動作,這樣一來,這個命令類將會達到預期的效果。
UML類圖
我們來看看命令模式的 uml 類圖:
命令模式的角色介紹:
- Receiver:接收者角色
該類負責具體實施或執行一個請求,說的通俗一點就是,執行具體邏輯的角色,以上面說到的“關機”操作命令為例,其接收者角色就是真正執行各項關機邏輯的底層代碼。任何一個類都能成為一個接收者,而接收者類中封裝具體操作邏輯的方法我們則稱為行動方法;
- Command :命令介面
定義所有具體命令類基本行為的抽象介面;
- ConreteCommand:具體命令角色
該類實現了 Command 介面,在 execute 方法中調用接收者角色的相關方法,在接收者和命令執行的具體行為之間加以弱耦合。而 execute 則通常稱為執行方法,如上面提到的“關機”操作實現,具體可能還包含很多相關的操作,比如儲存資料、關閉檔案、結束進程等,如果將這一些列的具體邏輯處理看作接收者,那麼調用這些具體邏輯的方法就可以看作是執行方法;
- Invoker :要求者角色
該類的職責就是調用命令對象執行具體的請求,相關的方法我們稱為行動方法,“關機”命令為例,關機這個命令一般就對應著一個關機方法,執行關機命令就相當於由這個關機方法去執行具體的邏輯,這個關機方法就可以看作是要求者;
- Client:用戶端角色
由此我們可以寫出命令模式的通用代碼:
Receiver.class 具體邏輯執行者
public class Receiver { public void action() { System.out.print("執行具體的操作"); }}
Command.class 抽象命令類
public interface Command { void execute();}
ConcreteCommand.clas 具體命令類
public class ConcreteCommand implements Command { private Receiver receiver; public ConcreteCommand(Receiver receiver) { this.receiver = receiver; } @Override public void execute() { receiver.action(); }}
Invoker.class 要求者
public class Invoker { private Command command; public Invoker(Command command) { this.command = command; } public void action() { command.execute(); }}
Client 用戶端
public class Client { public static void main(String[] args) { Receiver receiver = new Receiver(); Command command = new ConcreteCommand(receiver); Invoker invoker = new Invoker(command); invoker.action(); }}
最後也能通過 Invoker 類調用到真正的 Receiver 執行邏輯了。
樣本與源碼
這就以一個簡單的控制電燈亮滅和門開關的情景為例:
Light.class 和 Door.class 實際控制類
public class Light { public void lightOn() { System.out.print("light on\n"); } public void lightOff() { System.out.print("light off\n"); }}
public class Door { public void doorOpen() { System.out.print("door open\n"); } public void doorClose() { System.out.print("door close\n"); }}
然後是電燈的控制類:
LightOnCommand.class 和 LightOffCommand.class
public class LightOnCommand implements Command{ public Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.lightOn(); }}
public class LightOffCommand implements Command{ public Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.lightOff(); }}
門的相關控制類:
DoorOpenCommand.class 和 DoorCloseCommand.class
public class DoorOpenCommand implements Command{ public Door door; public DoorOpenCommand(Door door) { this.door = door; } @Override public void execute() { door.doorOpen(); }}
public class DoorCloseCommand implements Command{ public Door door; public DoorCloseCommand(Door door) { this.door = door; } @Override public void execute() { door.doorClose(); }}
然後是一個無操作預設命令類:
NoCommand.class
public class NoCommand implements Command{ @Override public void execute() { }}
最後是控制類:
Controller.class
public class Controller { private Command[] onCommands; private Command[] offCommands; public Controller() { onCommands = new Command[2]; offCommands = new Command[2]; Command noCommand = new NoCommand(); onCommands[0] = noCommand; onCommands[1] = noCommand; offCommands[0] = noCommand; offCommands[1] = noCommand; } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void onCommand(int slot) { onCommands[slot].execute(); } public void offCommand(int slot) { offCommands[slot].execute(); }}
測試代碼
Light light = new Light();Door door = new Door();LightOnCommand lightOnCommand = new LightOnCommand(light);LightOffCommand lightOffCommand = new LightOffCommand(light);DoorOpenCommand doorOpenCommand = new DoorOpenCommand(door);DoorCloseCommand doorCloseCommand = new DoorCloseCommand(door);Controller controller = new Controller();controller.setCommand(0, lightOnCommand, lightOffCommand);controller.setCommand(1, doorOpenCommand, doorCloseCommand);controller.onCommand(0);controller.offCommand(0);controller.onCommand(1);controller.offCommand(1);
結果:
這樣就實現了對 Light 和 Door 的控制了,其實這個例子只是實現了對命令模式的基本架構而已,命令模式的用處其實在於它的日誌和復原撤銷功能,每次執行命令的時候都列印出相應的關鍵日誌,或者每次執行後都將這個命令儲存進列表中並且每個命令實現一個 undo 方法,以便可以進行復原。另外,也可以構造一個 MacroCommand 宏命令類用來按次序先後執行幾條相關聯命令。當然可以發散的空間很多很多,感興趣的可以自己去實現,原理都是一樣的。
總結
命令模式將發出請求的對象和執行請求的對象解耦,被解耦的兩者之間通過命令對象進行溝通,調用者通過命令對象的 execute 放出請求,這會使得接收者的動作被調用,調用者可以接受命令當作參數,甚至在運行時動態地進行,命令可以支援撤銷,做法是實現一個 undo 方法來回到 execute 被執行前的狀態。MacroCommand 宏命令類是命令的一種簡單的延伸,允許調用多個命令,宏方法也可以支援撤銷。日誌系統和事務系統可以用命令模式來實現。
命令模式的優點很明顯,調用者和執行者之間的解耦,更靈活的控制性,以及更好的擴充性等。缺點更明顯,就是類的爆炸,大量衍生類的建立,這也是大部分設計模式的“通病”,是一個沒有辦法避免的問題。
源碼下載
https://github.com/zhaozepeng/Design-Patterns/tree/master/CommandPattern
引用
https://en.wikipedia.org/wiki/Command_pattern
http://blog.csdn.net/jason0539/article/details/45110355
java/android 設計模式學習筆記(16)---命令模式