設計模式學習之Command模式
今天學習了命令模式,這個模式非常的給力,它能將命令封裝起來讓另外的執行者去執行,相當于一個命令的收集和轉發過程,而且,這個模式還能將一系列的命令組合成“命令宏”,并且可以輕松的完成撤銷的操作,非常適合日志系統或事務處理系統,也常用于窗口編程中的菜單命令處理,好了,下面回顧一下這個模式吧
- 命令模式:
將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支持可撤消的操作。 - 適用性:
- 抽象出待執行的動作以參數化某對象,你可用過程語言中的回調(callback)函數表達這種參數化機制。所謂回調函數是指函數先在某處注冊,而它將在稍后某個需要的時候被調用。Command 模式是回調機制的一個面向對象的替代品。
- 在不同的時刻指定、排列和執行請求。一個Command 對象可以有一個與初始請求無關的生存期。如果一個請求的接收者可用一種與地址空間無關的方式表達,那么就可將負責該請求的命令對象傳送給另一個不同的進程并在那兒實現該請求。
- 支持取消操作。Command 的Excute 操作可在實施操作前將狀態存儲起來,在取消操作時這個狀態用來消除該操作的影響。Command 接口必須添加一個Unexecute 操作,該操作取消上一次Execute 調用的效果。執行的命令被存儲在一個歷史列表中。可通過向后和向前遍歷這一列表并分別調用Unexecute 和Execute 來實現重數不限的“取消”和“重做”。
- 支持修改日志,這樣當系統崩潰時,這些修改可以被重做一遍。在Command 接口中添加裝載操作和存儲操作,可以用來保持變動的一個一致的修改日志。從崩潰中恢復的過程包括從磁盤中重新讀入記錄下來的命令并用Execute 操作重新執行它們。
- 用構建在原語操作上的高層操作構造一個系統。這樣一種結構在支持事務( transaction )的信息系統中很常見。一個事務封裝了對數據的一組變動。Command 模式提供了對事務進行建模的方法。Command 有一個公共的接口,使得你可以用同一種方式調用所有的事務。同時使用該模式也易于添加新事務以擴展系統。
這個模式怎么舉例呢?既然是命令模式,那么就找一些需要發送命令的場合吧,最常見的當然是軟件中的菜單了,每一個菜單對應一個命令,當用戶點擊菜單的時候就會調用與菜單相關聯的命令了,那么怎么來使用命令模式構建菜單呢,看代碼:
//菜單接口 public interface IMenu { public Item getItem(int itemIndex); public void addItem(Item item); public int getItemLength(); public void setItem(int itemIndex, Command command); public void isClicked(int itemIndex); } public class Menu implements IMenu { protected Item[] items; protected int itemLength; private String title; public Menu(String title) { this.title=title; } public Item getItem(int itemIndex) throws OverflowException { if(itemIndex<0 || itemIndex>itemLength-1) { throw new OverflowException("out of range"); } return items[itemIndex]; } public void addItem(Item item) { if(item != NullItem) { items.add(item); } } public void setItem(int itemIndex, ICommand command) { if(itemIndex<0 || itemIndex>itemLength-1) { throw new OverflowException("out of range"); } Items[itemIndex].setCommand(command); } public void isClicked(int itemIndex) { this.getItem(itemIndex).isClicked(); } } //菜單項目 public class Item { private Command command; private String title; public Item(String title) { this.title=title; } public void setCommand(ICommand command) { this.command=command; } public void isClicked() { this.command.execute(); } } //命令接口 public interface ICommand { public void execute(); } //保存命令 public class SaveCommand implements ICommand { Document document; public SaveCommand(Document document)//document參數為接受者 { this.document=document; } public void execute() { if(document.isModified()) { document.save(); } } } //打開命令 public class OpenCommand implements ICommand { Application application; public OpenCommand(Application application) { this.application=application; } public void execute() { Document document=application.open(); application.addToWorkplace(document); } } //退出命令 public class ExitCommand implements ICommand { Application application; public ExitCommand(Application application) { this.application=application; } public void execute() { Document document; for(document=Application.currentDocument(); document!=NullDocument; document=Application.nextDocument()) { if(document.isModified()) { document.alertSave("您還有修改過的文檔尚未保存,是否保存?"); } } application.exit(); } } //剪切命令 public class CutCommand implements ICommand { SelectedBlock selectedBlock; public CutCommand(SelectedBlock selectedBlock) { this.selectedBlock=selectedBlock; } public void execute() { System.Clipboard.save(selectedBlock); selectedBlock.delete(); } } //粘貼命令 public class PasteCommand implements ICommand { Document document; Object pasteObject public PasteCommand(Document document, Object pasteObject) { this.document=document; this.pasteObject=pasteObject; } public void execute() { document.insertAt(document.currentCursor(), pasteObject); } } public class SaveAllCommand implements ICommand { Application application; public SaveAllCommand(Application application) { this.application=application; } public void execute() { Document document; for(document=application.currentDocument(); document!=NullDocument; document=application.nextDocument()) { if(document.isModified()) { document.save(); } } } } //客戶代碼 public class Client { public static void main(String[] argv) { /* * 創建編輯器和新建文檔 */ Application application=new Application("文件編輯器"); Document document=application.new("命令模式日志"); application.addToWorkplace(document); /* * 創建菜單,項目,命令對象 */ Menu menuFile=new Menu("文件菜單"); Menu menuEdit=new Menu("編輯菜單"); Item itemSave=new Item("保存"); Item itemOpen=new Item("打開"); Item itemExit=new Item("退出"); menuFile.addItem(itemSave); menuFile.addItem(itemOpen); menuFile.addItem(itemExit); Item itemCut=new Item("剪切"); Item itemPaste=new Item("粘貼"); menuEdit.addItem(itemCut); menuEdit.addItem(itemPaste); ICommand commandSave=SaveCommand(document); ICommand commandOpen=OpenCommand(application); ICommand commandExit=ExitCommand(application); ICommand commandCut=CutCommand(document.selectedBlock()); ICommand commandPaste=PasteCommand(document, System.clipboard.currentObject); /* * 賦予每個項目相應的命令 */ menuFile.setCommand(0, commandSave); menuFile.setCommand(1, commandOpen); menuFile.setCommand(2, commandExit); menuEdit.setCommand(0, commandCut); menuEdit.setCommand(1, commandPaste); /* * 模擬菜單操作 */ menuFile.isClicked(0);//點擊保存 menuFile.isClicked(1);//點擊打開 //用鼠標選中一行文字 menuEdit.isClicked(0);//點擊剪切 //將光標移動到任意位置 menuEdit.isClicked(1);//點擊粘貼 menuFile.isClicked(2);//點擊退出 } }
對于以上設計,是如何運用命令模式的呢?解釋一下吧,首先,命令模式有一個公共的命令接口:
//命令接口 public interface ICommand { public void execute(); }
該接口只有一個方法:execute(),這個方法提供給調用者最為簡潔的使用該命令的方式,用戶根本不需要關心他發出的命令的具體實現細節,他只需要構造一個具體的命令對象并把命令的接收者告訴該對象,然后調用該命令的execute方法即可,命令對象自然心領神會的按照客戶的要求將命令傳達給接收者,接收者收到命令后具體的執行。在上述代碼中,客戶創建了菜單并往菜單中添加了具體的菜單項,緊接著又創建了各種命令對象并將命令對象和菜單項綁定到一起,即調用setCommand函數。在創建命令對象的時候,會根據不同的命令對象所需的接收者不同給他們傳遞不同的接收者對象,比如ExitCommand需要Application類型的接收者,而CutCommand需要SelectedBlock類型的接收者,所以分別傳給他們對應類型的接收者對象,最后,由用戶操作菜單,使得對應的被點擊的菜單項的isClicked事件觸發,從而觸發對應命令的execute()方法來執行響應的命令。這就是整個命令模式的實現過程。
另外,命令模式其實還可以實現更多的功能,在上面的代碼中,命令接口只有一個execute()方法,其實,還可以增加一個undo()方法來執行命令的撤銷工作,如果實現了該方法,那么就能對對應的操作進行撤銷操作了,此處就不詳述了。
還有一個很給力的地方就是,命令可以組合!這是命令模式的精華所在,如果用過office的宏功能就會知道,一個宏可以一下子執行很多的命令,其實這種宏功能就是一種命令的組合,在定義的上述的命令模式后,完全可以定義一個宏命令,比如上面的例子中我想定義一個宏命令,操作次序如下:打開文檔->粘貼->保存->退出程序(很無聊吧,
),就可以這樣實現
//宏命令 public class MacroCommand implements ICommand { ICommand[] commands; public MacroCommand(ICommand[] commands) { this.commands=commands; } public void execute() { for(int i=0; i<commands.length, i++) { commands[i].execute(); } } } //客戶代碼 public class Client { public static void main(String[] argv) { /* * 創建編輯器和新建文檔 */ Application application=new Application("文件編輯器"); Document document=application.new("命令模式日志"); application.addToWorkplace(document); /* * 創建菜單,項目,命令對象 */ ... ICommand commandSave=SaveCommand(document); ICommand commandOpen=OpenCommand(application); ICommand commandExit=ExitCommand(application); ICommand commandCut=CutCommand(document.selectedBlock()); ICommand commandPaste=PasteCommand(document, System.clipboard.currentObject); /* * 賦予每個項目相應的命令 */ ... /* / 模擬宏操作 */ ICommand commands={commandOpen, commandPaste, commandSave, commandExit}; ICommand macroCommand=MacroCommand(commands); macroCommand.execute(); } }
是不是很酷?
作者:everdom
出處:http://everdom.cnblogs.com/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。

浙公網安備 33010602011771號