命令模式(Command)的两种不同实现-阿里云开发者社区

开发者社区> 技术小阿哥> 正文

命令模式(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)进行设计。


本文转自 xxxx66yyyy 51CTO博客,原文链接:http://blog.51cto.com/haolloyin/339076,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
10003 0
命令模式(Command)
请求发送者与接收者解耦——命令模式(一)请求发送者与接收者解耦——命令模式(二)请求发送者与接收者解耦——命令模式(三)请求发送者与接收者解耦——命令模式(四)请求发送者与接收者解耦——命令模式(五)请求发送者与接收者解耦——命令模式(六) ...
480 0
【编程模式】(一) ------ 命令模式 和 “重做” 及 “撤销”
前言 本文及以后该系列的篇章都是本人对 《游戏编程模式》这本书的阅读理解,从中对一些原理,用更直白的语言描述出来,并对部分思路或功能进行初步实现。而本文所描述的 命令模式, 相信读者应该都有了解过或听说过,如果尚有疑惑的读者,我希望本文能对你有所帮助。
1021 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
13798 0
[译]ASP.NET Core中使用MediatR实现命令和中介者模式
原文:[译]ASP.NET Core中使用MediatR实现命令和中介者模式 作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9866068.html 在本文中,我将解释命令模式,以及如何利用基于命令模式的第三方库来实现它们,以及如何在ASP.NET Core中使用它来解决我们的问题并使代码简洁。
1356 0
13694
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载
《2021云上架构与运维峰会演讲合集》
立即下载