设计模式学习之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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。