0.参考资料



1.概述

  • 将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。 —《设计模式》GoF
  • 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计

1.1动机

  1. - 在软件构建过程中,“行为请求者” 与“行为实现者”通常呈现一种 "紧耦合"。但在某些场合 -- 比如需要对行为进行"记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
  2. - 在这种情况下,如何将"行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

1.2结构

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1629882724266-48734264-6305-4fff-a8cf-9599f41a5a6b.png#clientId=u7075f660-2c7e-4&from=paste&height=225&id=u8ece9412&margin=%5Bobject%20Object%5D&name=image.png&originHeight=450&originWidth=1214&originalType=binary&ratio=1&size=184752&status=done&style=none&taskId=u29650f68-8cfe-4862-8999-ad6284e01e9&width=607)
  2. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145304044-b5609c05-3675-4912-8e62-ca741a4d6da6.png#clientId=uac1adc34-039b-4&from=paste&height=210&id=u27f2a38b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=280&originWidth=567&originalType=binary&ratio=1&size=14961&status=done&style=none&taskId=u3fe98bfb-4c09-4e54-a408-b1a27974981&width=425)
  3. - 对原理类图的说明-即(命名模式的角色及职责)
  4. - 1) Invoker 是调用者角色
  5. - 2) Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
  6. - 3) Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
  7. - 4) ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现execute

2.要点总结

宏观架构

  1. 1. Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦, 在面向对象语言中,常见的实现手段是”将行为抽象为对象”
  2. 1. 实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息。 通过使用Composite模式 ,可以将多个”命令”封装为一个个"复合命令" MacroCommand.
  3. 1. Command模式与C+ +中的函数对象有些类似。但两者定义行为接口的规范有所区别: Command以面向对象中的"接口-实现” 来定义行为接口规范,更严格,但有性能损失; C+ +函数对象以函数签名来定义行为接口规范,更灵活,性能更高。

微观代码

  1. 1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要

调用命令对象的execute()方法就可以让接收者工作,而不必知道具体的接收者对 象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:” 请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到 了纽带桥梁的作用。

  1. 1. 可以设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  2. 1. 方便实现对请求的撤销和重做
  3. 1. **命令模式不足:**可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这

点在在使用的时候要注意

  1. 1. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没

有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。

  1. 1. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟CMDDOS命令) 订单的撤销/恢复、触发-反馈机制

3.案例

需求

  1. 1. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装app就可以控制对这些家电工作。
  2. 1. 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个App,分别控制,我们希望只要一个app就可以控制全部智能家电。
  3. 1. 要实现一个app控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给app调用

分析

4.使用模式

方案

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145463524-a464c6a0-b35e-4fb2-910e-89e3ae3ce65f.png#clientId=u938b4e9e-05d4-4&from=paste&height=243&id=u1680866f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=323&originWidth=230&originalType=binary&ratio=1&size=13140&status=done&style=none&taskId=u139498e6-4020-4d9e-bd9f-3d60b85cef5&width=173)

类图

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145521690-6129199e-4307-4e25-903c-83a800dbf6c3.png#clientId=u938b4e9e-05d4-4&from=paste&height=242&id=u7cd63d57&margin=%5Bobject%20Object%5D&name=image.png&originHeight=484&originWidth=1113&originalType=binary&ratio=1&size=247496&status=done&style=none&taskId=u2a018237-c582-4ff1-bc1f-719dcd664e4&width=556.5)
  2. - ![Command.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630145625892-6e201e4a-c95c-4a3f-8edf-fa571b974e7c.png#clientId=u938b4e9e-05d4-4&from=ui&id=ub9f4620a&margin=%5Bobject%20Object%5D&name=Command.png&originHeight=922&originWidth=2476&originalType=binary&ratio=1&size=70400&status=done&style=none&taskId=uf55de3dd-31f9-459f-a9a3-bd266ca5e40)

代码

  1. - Command
  1. // 命令接口
  2. public interface Command {
  3. // 执行命令
  4. void execute();
  5. // 撤销命令
  6. void undo();
  7. }
  8. // part1 的 on 命令
  9. public class ConcreteCommand_part1_on implements Command {
  10. Part1 part1;
  11. public ConcreteCommand_part1_on(Part1 part1){
  12. this.part1 = part1;
  13. }
  14. @Override
  15. public void execute() {
  16. part1.on();
  17. }
  18. @Override
  19. public void undo() {
  20. part1.off();
  21. }
  22. }
  23. // part1 的 off 命令
  24. public class ConcreteCommand_part1_off implements Command {
  25. Part1 part1;
  26. public ConcreteCommand_part1_off(Part1 part1){
  27. this.part1 = part1;
  28. }
  29. @Override
  30. public void execute() {
  31. part1.off();
  32. }
  33. @Override
  34. public void undo() {
  35. part1.on();
  36. }
  37. }
  38. // part2....
  39. public class ConcreteCommand_part2_on implements Command {
  40. Part2 part2;
  41. public ConcreteCommand_part2_on(Part2 part2){
  42. this.part2 = part2;
  43. }
  44. @Override
  45. public void execute() {
  46. part2.on();
  47. }
  48. @Override
  49. public void undo() {
  50. part2.off();
  51. }
  52. }
  53. public class ConcreteCommand_part2_off implements Command {
  54. Part2 part2;
  55. public ConcreteCommand_part2_off(Part2 part2){
  56. this.part2 = part2;
  57. }
  58. @Override
  59. public void execute() {
  60. part2.off();
  61. }
  62. @Override
  63. public void undo() {
  64. part2.on();
  65. }
  66. }
  1. - 接收者
  1. // 可忽略的开关接口
  2. public interface Openable {
  3. void on();
  4. void off();
  5. }
  6. public class Part1 implements Openable {
  7. @Override
  8. public void on() {
  9. System.out.println("part1 开");
  10. }
  11. @Override
  12. public void off() {
  13. System.out.println("part1 关");
  14. }
  15. }
  16. public class Part2 implements Openable {
  17. @Override
  18. public void on() {
  19. System.out.println("part2 开");
  20. }
  21. @Override
  22. public void off() {
  23. System.out.println("part2 关");
  24. }
  25. }
  1. - 调用者
  1. public class MyInvoke {
  2. // 两条集合管理所有开关按钮
  3. List<Command> onCommands = new ArrayList<>(10);
  4. List<Command> offCommands = new ArrayList<>(10);
  5. // 记录的可撤销一次
  6. Command undoCommand;
  7. // 添加组件
  8. public void addCommand(int index, Command onCommand, Command offCommand){
  9. onCommands.add(index, onCommand);
  10. offCommands.add(index, offCommand);
  11. }
  12. // 按下某组件的开按钮,
  13. public void onButtonForIndex(int index){
  14. onCommands.get(index).execute();
  15. // 记录操作, 以撤销
  16. undoCommand = onCommands.get(index);
  17. }
  18. // 按下某组件的关按钮,
  19. public void offButtonForIndex(int index){
  20. offCommands.get(index).execute();
  21. // 记录操作, 以撤销
  22. undoCommand = offCommands.get(index);
  23. }
  24. // 撤销操作
  25. public void undo(){
  26. undoCommand.undo();
  27. }
  28. }
  1. - 测试
  1. public class Client {
  2. public static void main(String[] args) {
  3. // 初始化一个(空)遥控器
  4. MyInvoke invoke = new MyInvoke();
  5. // 创建接收者一号: part1, 二号
  6. Part1 part1 = new Part1();
  7. Part2 part2 = new Part2();
  8. // 创建接收者对应的开关命令
  9. Command part1_on_cmd = new ConcreteCommand_part1_on(part1);
  10. Command part1_off_cmd = new ConcreteCommand_part1_off(part1);
  11. Command part2_on_cmd = new ConcreteCommand_part2_on(part2);
  12. Command part2_off_cmd = new ConcreteCommand_part2_off(part2);
  13. // 开关命令填充进遥控器
  14. invoke.addCommand(0, part1_on_cmd, part1_off_cmd);
  15. invoke.addCommand(1, part2_on_cmd, part2_off_cmd);
  16. // 使用测试
  17. System.out.println("\t按下 part1 的 on:");
  18. invoke.onButtonForIndex(0);
  19. System.out.println("\t按下 part1 的 off:");
  20. invoke.offButtonForIndex(0);
  21. System.out.println("\t 撤回:...");
  22. invoke.undo();
  23. System.out.println("\t按下 part2 的 on:");
  24. invoke.onButtonForIndex(1);
  25. System.out.println("\t按下 part2 的 off:");
  26. invoke.offButtonForIndex(1);
  27. System.out.println("\t 撤回:...");
  28. invoke.undo();
  29. }
  30. }
  1. 按下 part1 on:
  2. part1
  3. 按下 part1 off:
  4. part1
  5. 撤回:...
  6. part1
  7. 按下 part2 on:
  8. part2
  9. 按下 part2 off:
  10. part2
  11. 撤回:...
  12. part2

5.经典使用


5.1Spring中JdbcTemplate

说明

  1. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630146789653-3861dc19-31cd-40dc-a913-fa46d74c2b91.png#clientId=u69226290-61b9-4&from=paste&height=236&id=u9bf4ad65&margin=%5Bobject%20Object%5D&name=image.png&originHeight=472&originWidth=1144&originalType=binary&ratio=1&size=672838&status=done&style=none&taskId=u8c0794cb-a6c3-4851-9349-dfe86f25857&width=572)

分析

  1. - StatementCallback 接口, 命令接口(Command). 其中 doInStatement(...) 即命令方法
  2. - class QueryStatementCallback implements StatementCallback, SqlProvider , 匿名内<br />部类, 实现了命令接口. 调用真正的执行方法,真正的执行方法即stmtexecuteQuery()。
  3. - JdbcTemplate即充当调用者角色,又充当接收者角色。
  4. - execute(StatementCallback action) ,调用者的调用方法. 调用了 (具体的的)action.doInStatement 方法.