设计模式之命令模式
在许多设计中,经常设计一个对象请求另一个对象执行某一个操作。如果请求者无法或者不希望直接和被请求者打交道,即请求对象无法或者不希望含有被请求者的引用,那么可以使用命令模式。命令模式里称提出请求的对象为请求者,被请求者的对象为接收者。在命令模式中,当一个对象请求另一个对象调用其方法时,不和被请求者直接打交道,而是把这种“请求”封装到一个“命令”对象中,封装的手段将“请求”封装到“命令”对象的一个方法中。命令模式的核心就是使用命令对象来封装方法调用。
例如,在军队作战时,指挥官要命令三连偷袭敌人。但是指挥官这时无法或者不希望和三连直接取得联系,那么指挥官可以发出一个命令,把该命令的执行者设置为三连。这样指挥官只要和命令打交道。
命令模式包含四种角色:
接收者:接收者是一个类的实例,该实例负责执行与请求相关的操作。
命令接口:命令式一个接口,规定了用来封装“请求”的若干个方法,比如execute()、undo()等方法。
具体命令:具体命令式实现了命令接口的类的实例,包含命令接口的方法。并且包含接受者的引用,指明那个对象去执行该命令。
请求者:请求者是包含命令接口变量类的实例,该接口变量可以存放任何具体命令的引用。请求者负责调用具体命令,让具体命令执行那些封装了的请求方法。
下面看军队作战的例子:
1.命令接收者:
1 package com.command; 2 //命令接收者、执行者 3 public class CompanyArmy { 4 public void sneakAttack(){ 5 System.out.println("我们知道如何袭击敌人,保证完成任务"); 6 } 7 }
2.命令接口:
1 package com.command; 2 //命令接口 3 public interface Command { 4 void execute(); 5 }
3.具体命令:
1 package com.command; 2 //具体命令 3 public class ConcreteCommand implements Command{ 4 CompanyArmy army; //含有接收者的引用 5 public ConcreteCommand(CompanyArmy army) { 6 this.army = army; 7 } 8 public void execute() { //封装着指挥官的请求 9 army.sneakAttack(); //偷袭敌人 10 } 11 }
4.请求者:
1 package com.command; 2 //请求者,也就是命令发送者 3 public class ArmySuperior { 4 Command command; //存放具体命令的引用 5 public void setCommand(Command command){ 6 this.command = command; 7 } 8 public void startExecuteCommand(){ 9 command.execute(); 10 } 11 }
命令模式所需要的四个角色已经建立好了,下面测试一下:
1 package com.command; 2 3 public class Application { 4 public static void main(String[] args) { 5 CompanyArmy army = new CompanyArmy(); //创建命令接收者 6 Command command = new ConcreteCommand(army);//创建一个具体命令并且指定接收者 7 ArmySuperior superior = new ArmySuperior(); //创建命令请求者 8 superior.setCommand(command); //给请求者设置一个具体命令 9 superior.startExecuteCommand(); //开始执行命令 10 } 11 }
这样就可以实现指挥官不直接和命令执行者,只需要发送一个命令,该命令就会找到指定的执行者去执行。这样大大降低了程序的耦合度。另外还有一个好处就是,要想发送另外一个命令只要再创建一个具体的命令即可,不需要修改其他代码,增强了程序的扩展性。
使用命令模式还有一个好处,就是可以撤销所执行的操作。也就是请求者发送一个请求,接收者执行后,还可以撤销该操作。以下使用一个简单的例子说明怎样在具体的命令中实现undo()方法。问题如下:
请求者请求在硬盘建立一个目录,请求成功后海可以撤销请求。这就要求接收者不仅可以在硬盘上建立目录,海可以删除上一次建立的目录。
1接收者:要包含两个方法,建立目录和删除目录
1 package com.command1; 2 import java.io.File; 3 //命令接收者 4 public class MakeDir { 5 //创建目录 6 public void createDir(String name){ 7 File dir = new File(name); 8 dir.mkdir(); 9 } 10 //删除目录 11 public void deleteDir(String name){ 12 File dir = new File(name); 13 dir.delete(); 14 } 15 }
2.命令接口
1 package com.command1; 2 //包含撤销的命令接口 3 public interface Command { 4 void execute(String name); 5 void undo(); 6 }
3.具体命令
1 package com.command1; 2 import java.util.ArrayList; 3 //具体命令 4 public class ConcreteCommand implements Command { 5 ArrayList<String> dirNameList; 6 MakeDir makeDir; 7 public ConcreteCommand(MakeDir makeDir) { 8 dirNameList = new ArrayList<String>(); 9 this.makeDir = makeDir; 10 } 11 public void execute(String name) { 12 makeDir.createDir(name); 13 dirNameList.add(name); 14 } 15 public void undo() { 16 if(dirNameList.size()>0){ 17 makeDir.deleteDir(dirNameList.get(dirNameList.size()-1)); 18 dirNameList.remove(dirNameList.size()-1); 19 }else{ 20 System.out.println("没有需要撤销的操作!"); 21 } 22 } 23 }
4.请求者
1 package com.command1; 2 //请求者 3 public class RequestMakeDir { 4 Command command; 5 public void setCommand(Command command) { 6 this.command = command; 7 } 8 public void startExecuteCommand(String name){ 9 command.execute(name); 10 } 11 public void undoCommand(){ 12 command.undo(); 13 } 14 }
包含撤销的命令模式的四个角色创建好了,下面写一个测试类:
package com.command1; public class Application { public static void main(String[] args) { MakeDir makeDir = new MakeDir(); //创建接收者 Command command = new ConcreteCommand(makeDir);//创建具体命令并且指定接收者 RequestMakeDir request = new RequestMakeDir(); //创建请求者 request.setCommand(command); //设置命令 request.startExecuteCommand("haha"); //创建目录 request.startExecuteCommand("hahaa"); request.undoCommand(); //撤销 request.undoCommand(); } }
这样就可以创建目录和撤销操作了。