定义

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

结构和说明

行为型模式-命令模式 - 图1

示例代码

  1. public class CommandDemo {
  2. /**
  3. * 命令接口, 申明执行的操作
  4. */
  5. public static interface Command {
  6. /**
  7. * 执行命令对应的操作
  8. */
  9. void execute();
  10. }
  11. public static class ConcreteCommand implements Command {
  12. /**
  13. * 持有相应的接收者对象
  14. */
  15. private Receiver receiver;
  16. /**
  17. * 示意,命令对象可以有自己的状态
  18. */
  19. private String state;
  20. /**
  21. * 构造方法,传入相应的接收者对象
  22. *
  23. * @param receiver 相应的接收者对象
  24. */
  25. public ConcreteCommand(Receiver receiver) {
  26. this.receiver = receiver;
  27. }
  28. @Override
  29. public void execute() {
  30. // 通常会转调接收者对象的相应方法,让接收者来真正执行功能
  31. receiver.action();
  32. }
  33. }
  34. /**
  35. * 接收者对象
  36. */
  37. public static class Receiver {
  38. /**
  39. * 示意方法,真正执行命令相应的操作
  40. */
  41. public void action() {
  42. // 真正执行命令操作的功能代码
  43. }
  44. }
  45. /**
  46. * 调用者
  47. */
  48. public static class Invoker {
  49. /**
  50. * 持有命令对象
  51. */
  52. @Setter
  53. private Command command;
  54. /**
  55. * 示意方法,要求命令执行请求
  56. */
  57. public void runCommand() {
  58. // 调用命令对象的执行方法
  59. command.execute();
  60. }
  61. }
  62. /**
  63. * 注意这个不是我们通常意义上的测试客户端,主要功能是要创建
  64. * 命令对象并设定它的接收者,因此这里并没有调用执行的代码
  65. */
  66. public static class Client {
  67. /**
  68. * 示意,负责创建命令对象,并设定它的接收者
  69. */
  70. public void assemble() {
  71. // 创建接收者
  72. Receiver receiver = new Receiver();
  73. // 创建命令对象,设定它的接收者
  74. Command command = new ConcreteCommand(receiver);
  75. // 创建 Invoker,把命令对象设置进去
  76. Invoker invoker = new Invoker();
  77. invoker.setCommand(command);
  78. }
  79. }
  80. }

调用顺序

组装过程

行为型模式-命令模式 - 图2

执行过程

行为型模式-命令模式 - 图3

优缺点

优点

  • 更松散的耦合

命令模式使得发起命令的对象-客户端,和具体实现命令的对象-接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。

  • 更动态的控制

命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更加灵活。

  • 很自然的复合命令

命令模式中的命令对象能够很容易地组合成复合命令,也就是前面讲的宏命令,从而使系统操作更简单,功能更强大。

  • 更好的扩展性

由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象配置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。

思考

本质

封装请求

何时选用

  • 如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
  • 如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式。将这些请求封装成为命令对象,然后实现将请求队列化。
  • 如果需要支持取消操作,可以选用命令模式,通过管理命令对象,能很容易地实现命令的恢复和重做功能。
  • 如果需要支持当系统奔溃时,能将系统的操作功能重新执行一边,可以选用命令模式。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
  • 在需要事务的系统中,可以选用命令模式。命令模式提供了对事务进行建模的方法。命令模式有一个别名就是 Transaction。

相关模式

  • 命令模式和组合模式

这两个模式可以组合使用
在命令模式中,实现宏命令的功能就可以使用组合模式来实现

  • 命令模式和备忘录模式

这两个模式可以组合使用。
在命令模式中,实现可撤销操作功能时,通过保存命令执行前的状态,撤销的时候就把状态恢复。如果采用这种方法实现,就可以考虑使用备忘录模式。
如果状态存储在命令对象中,那么还可以使用原型模式,把命令对象当作原型来克隆一个新的对象,然后将克隆出来的对象通过备忘录模式存放。

  • 命令模式和模版方法模式

这两个模式从某种意义上有相似的功能,命令模式可以作为模版方法的一种替代模式,也就是说命令模式可以模仿实现模版方法模式的功能。
如果前面讲述的退化的命令模式可以实现 Java 的回调,而 Invoker 智能化后向服务进化,如果 Invoker 的方法是一个算法骨架,其中有两步在这个骨架里面没有具体实现,需要外部来实现,这个时候就可以通过回调命令接口来实现。而类似的功能在模版方法中,是先调用抽象方法,然后等待子类来实现。
可以看出虽然实现方式不一样,但是可以实现相同的功能。