命令模式的两种不同实现

转载自 命令模式(Command)的两种不同实现

命令模式(Command:将一个请求封装成一个对象,使得你用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
 
命令模式,顾名思义来理解即可,就是客户端发布一个命令(也就是“请求”),而这个命令是已经被封装成一个对象的。即这个命令对象的内部可能已经指定了该命令具体被谁负责执行。就像开发经理从客户那边获取对方的需求(命令),客户在描述具体的需求可以决定是否明确指出该需求的执行方。
 
命令模式的通用类图如下: 

 
上图中,Invoker 类就相当于开发经理,ConcreteCommand 类是具体的命令,它继承自抽象命令类 Command 类,该抽象类中定义了每个命令被执行的方法 execute() 。Receiver 抽象类定义了对每一个具体的命令的执行方法 action() ,一旦接收到命令则立即行动。这里应该注意的是,每个具体的命令类都必须指定该命令的接收者,否则这命令发布了也没相应的人来完成,那就没戏了。
 
具体代码实现如下:
命令接收者相关类:
  1. //抽象接收者,定义了每个接收者应该完成的业务逻辑  
  2. abstract class AbstractReceiver {  
  3.     public abstract void doJob();  
  4. }  
  5.  
  6. // 具体接收者01,实现自己真正的业务逻辑  
  7. class Receiver01 extends AbstractReceiver {  
  8.     public void doJob() {  
  9.         System.out.println("接收者01 完成工作 ...\n");  
  10.     }  
  11. }  
  12.  
  13. // 具体接收者02,实现自己真正的业务逻辑  
  14. class Receiver02 extends AbstractReceiver {  
  15.     public void doJob() {  
  16.         System.out.println("接收者02 完成工作 ...\n");  
  17.     }  
命令类:
  1. // 抽象命令类,定义了每个具体命令被执行的入口方法execute()  
  2. abstract class AbstractCommand {  
  3.     public abstract void execute();  
  4. }  
  5.  
  6. // 具体命令类01,通过构造函数的参数决定了该命令由哪个接收者执行  
  7. class Command01 extends AbstsractCommand {  
  8.     private AbstractReceiver receiver = null;  
  9.  
  10.     public Command01(AbstractReceiver receiver) {  
  11.         this.receiver = receiver;  
  12.     }  
  13.  
  14.     public void execute() {  
  15.               System.out.println("命令01 被发布 ...");  
  16.         this.receiver.doJob();  
  17.     }  
  18. }  
  19.  
  20. // 具体命令类02,通过构造函数的参数决定了该命令由哪个接收者执行  
  21. class Command02 extends AbstractCommand {  
  22.     private AbstractReceiver receiver = null;  
  23.  
  24.     public Command02(AbstractReceiver receiver) {  
  25.         this.receiver = receiver;  
  26.     }  
  27.  
  28.     public void execute() {  
  29.               System.out.println("命令02 被发布 ...");  
  30.         this.receiver.doJob();  
  31.     }  
调用者类:
  1. // 调用者,负责将具体的命令传送给具体的接收者  
  2. class Invoker {  
  3.     private AbstractCommand command = null;  
  4.  
  5.     public void setCommand(AbstractCommand command) {  
  6.         this.command = command;  
  7.     }  
  8.  
  9.     public void action() {  
  10.         this.command.execute();  
  11.     }  
测试类:
  1. //测试类  
  2. public class Client {  
  3.     public static void main(String[] args) {  
  4.         // 创建调用者  
  5.         Invoker invoker = new Invoker();  
  6.  
  7.         // 创建一个具体命令,并指定该命令被执行的具体接收者  
  8.         AbstractCommand command01 = new Command01(new Receiver01());  
  9.  
  10.         // 给调用者发布一个具体命令  
  11.         invoker.setCommand(command01);  
  12.  
  13.         // 调用者执行命令,其实是将其传送给具体的接收者并让其真正执行  
  14.         invoker.action();  
  15.           
  16.         AbstractCommand command02 = new Command01(new Receiver02());  
  17.         invoker.setCommand(command02);  
  18.         invoker.action();  
  19.     }  
测试结果:
命令01 被发布 ...
接收者01 完成工作 ...
 
命令02 被发布 ...
接收者02 完成工作 ...
 
如上面测试中输出的结果,我们知道在客户端中每次声明并创建一个具体的命令对象时总要显式地将其指定给某一具体的接收者(也就是命令的最终执行者),这似乎不太灵活,在现实中有些命令的发布也确实不是预先就指定了该命令的接收者的。
 
我们可以修改一下类图,使得客户端在有必要的时候才显式地指明命令的接收者,如下:

 
较之第一个通用类图,这里的客户 Client 类不直接与接收者 Receiver 类相关,而仅仅与调用者 Invoker 类有联系,客户发布下来的一个命令或者请求,只需要到了调用者Invoker 这里就停止了,具体怎么实现,不必对客户公开,由调用者分配即可。这里的 Command 抽象类将子类中指定具体接收者的构造函数的逻辑提取出来,由具体子类提供通过调用父类构造函数的无参、有参构造函数来实现。主要修改的是命令相关的类。 
 
具体逻辑请看下面的代码实现:
命令类:
  1. /*  
  2.  * 抽象命令类,使用构造函数的传入参数预先内定具体接收者, 若想使用其他接收者,可在子类的构造函数中传入  
  3.  */ 
  4. abstract class AbstractCommand {  
  5.     protected AbstractReceiver receiver = null;  
  6.  
  7.     public AbstractCommand(AbstractReceiver receiver) {  
  8.         this.receiver = receiver;  
  9.     }  
  10.  
  11.     public abstract void execute();  
  12. }  
  13.  
  14. // 具体命令类01,提供无参、有参两种构造函数  
  15. class Command01 extends AbstractCommand {  
  16.  
  17.     // 使用无参构造函数来默认使用的具体接收者  
  18.     public Command01() {  
  19.         super(new Receiver01());  
  20.     }  
  21.  
  22.     // 使用有参构造函数来指定具体的接收者  
  23.     public Command01(AbstractReceiver receiver) {  
  24.         super(receiver);  
  25.     }  
  26.  
  27.     public void execute() {  
  28.               System.out.println("命令01 被发布 ...")  
  29.         this.receiver.doJob();  
  30.     }  
  31. }  
  32.  
  33. // 具体命令类02,提供无参、有参两种构造函数  
  34. class Command02 extends AbstractCommand {  
  35.  
  36.     // 使用无参构造函数来默认使用的具体接收者  
  37.     public Command02() {  
  38.         super(new Receiver02());  
  39.     }  
  40.  
  41.     // 使用有参构造函数来指定具体的接收者  
  42.     public Command02(AbstractReceiver receiver) {  
  43.         super(receiver);  
  44.     }  
  45.  
  46.     public void execute() {  
  47.               System.out.println("命令02 被发布 ...")  
  48.         this.receiver.doJob();  
  49.     }  
修改后的测试类:
  1. // 测试类  
  2. public class Client {  
  3.     public static void main(String[] args) {  
  4.         // 创建调用者  
  5.         Invoker invoker = new Invoker();  
  6.  
  7.         // 创建一个具体命令,并指定该命令被执行的具体接收者  
  8. //      AbstractCommand command01 = new Command01(new Receiver01());  
  9.         AbstractCommand command01 = new Command01();  
  10.  
  11.         // 给调用者发布一个具体命令  
  12.         invoker.setCommand(command01);  
  13.  
  14.         // 调用者执行命令,其实是将其传送给具体的接收者并让其真正执行  
  15.         invoker.action();  
  16.           
  17. //      AbstractCommand command02 = new Command01(receiver02);  
  18.         AbstractCommand command02 = new Command02();  
  19.         invoker.setCommand(command02);  
  20.         invoker.action();  
  21.           
  22.         System.out.println("\n设置命令01由接收者02执行...");  
  23.         command01 = new Command01(new Receiver02());  
  24.         invoker.setCommand(command01);  
  25.         invoker.action();  
  26.     }  
测试结果:
命令01 被发布 ...
接收者01 完成工作 ...
 
命令02 被发布 ...
接收者02 完成工作 ...
 
设置命令01由接收者02执行...
命令01 被发布 ...
接收者02 完成工作 ...
 

此时在客户端中,我们不指明一个命令的具体接收者(执行者)也同样可以达到第一种实现方法中的效果。此外,客户也可以显式指出具体接收者,就像上面那样。

命令模式的优点:
1、 调用者与接收者没有任何的依赖关系,它们时通过具体的命令的存在而存在的;
2、 若有多个具体命令,只要扩展 Command 的子类即可,同样地具体接收者也可以相对应地进行扩展;
 
命令模式的缺点:其实上面优点中第2点在一定场景中也会变成缺点。如果具体的命令有很多个,那么子类就必然会暴增、膨胀。
 
但是,上面的具体代码实现中这种设计似乎也不太乐观,原因是每一个具体的命令都是由一个具体的接收者来执行的,在多交互的场景中这显然是太理想化的。于是,我想到了中介者模式(Mediator)中最主要的 Mediator 类中预先地注册了业务中需要交互的同事类的对象,接下来每一次交互逻辑都交给 Mediator 来“暗箱”操作。
 
根据上一段的想法,我们可以在抽象命令 Command 类中预先注册一定数量的具体接收者,那么具体命令中就可以决定是否要在多个接收者中进行协作完成了,这种协作的代码逻辑则应该写在覆盖父类的execute() 方法中,而在 execute() 方法中又可以运用模板方法模式(Template Method)进行设计。
阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页