:::info 此模式在日常开发中使用频率不高,但关键时刻是能起大作用的。 :::

含义

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

使用场景

  • 当需要将各种执行的动作抽象出来,使用时通过不同的参数来决定执行哪个对象
  • 当某个或者某些操作需要支持撤销的场景
  • 当要对操作过程记录日志,以便后期通过日志将操作过程重新做一遍时
  • 当某个操作需要支持事务操作的时候

    UML

    命令模式 - 图1

    角色结构

  • Command:是一个接口或者抽象类,定义一个命令。

  • ConcreteCommand:具体的执行命令,他们需要实现Command接口或者继承抽象类。
  • Receiver:真正执行命令的角色,那些具体的命令引用它,让它完成命令的执行。
  • Invoker:负责按照客户端的指令设置并执行命令,像命令的撤销,日志的记录等功能都要在此类中完成。

    优点

  • 调用操作与具体执行者解耦。

  • 添加一个命令非常容易。
  • 很容易实现序列操作及实现回调系统。

    缺点

  • 使用命令模式可能会导致某些系统有过多的具体命令类。

    业务场景

    当我们看电视的时候,有时候插播广告就很烦人,于是我们就在广告的时候去切到其他频道去看,遥控器上既有后退按钮也有前进按钮,对应的是我们的undo和redo操作。

    代码示例

    Command 命令抽象类

    所有具体的命令都会实现此接口,Invoker只认识此接口,该类持有了命令的执行者TV

    1. public abstract class Command {
    2. /**
    3. * 命令接收者:电视机
    4. */
    5. protected Television television;
    6. public Command(Television television) {
    7. this.television = television;
    8. }
    9. /**
    10. * 执行命令
    11. */
    12. public abstract void execute();
    13. }

    Television 电视机对象:提供了播放不同频道的方法

    ```java public class Television { public void playCctv1() {

    1. System.out.println("--CCTV1--");

    }

    public void playCctv2() {

    1. System.out.println("--CCTV2--");

    }

    public void playCctv3() {

    1. System.out.println("--CCTV3--");

    }

    public void playCctv4() {

    1. System.out.println("--CCTV4--");

    }

    public void playCctv5() {

    1. System.out.println("--CCTV5--");

    }

}

  1. <a name="EuRiG"></a>
  2. #### Invoker 构建命令的调用者
  3. ```java
  4. public class Invoker {
  5. /**
  6. * 当前播放
  7. */
  8. Command command;
  9. /**
  10. * 播放记录
  11. */
  12. Stack<Command> unStack = new Stack<>();
  13. Stack<Command> reStack = new Stack<>();
  14. /**
  15. * 切换卫视
  16. */
  17. public void switchCommand(Command command) {
  18. if (this.command != null) {
  19. unStack.push(this.command);
  20. }
  21. this.command = command;
  22. command.execute();
  23. }
  24. /**
  25. * 遥控器返回命令
  26. */
  27. public void undo() {
  28. if (unStack.isEmpty()) {
  29. return;
  30. }
  31. //获取上一个播放某卫视的命令
  32. Command command = unStack.pop();
  33. reStack.push(this.command);
  34. this.command = command;
  35. command.execute();
  36. }
  37. /**
  38. * 遥控器恢复命令
  39. */
  40. public void redo() {
  41. if (reStack.isEmpty()) {
  42. return;
  43. }
  44. //获取上一个播放某卫视的命令
  45. Command command = reStack.pop();
  46. unStack.push(this.command);
  47. this.command = command;
  48. command.execute();
  49. }
  50. }

CCTV1Command… 命令的实现者

  1. public class CCTV1Command extends Command {
  2. public CCTV1Command(Television television) {
  3. super(television);
  4. }
  5. @Override
  6. public void execute() {
  7. television.playCctv1();
  8. }
  9. }
  1. public class CCTV2Command extends Command {
  2. public CCTV2Command(Television television) {
  3. super(television);
  4. }
  5. @Override
  6. public void execute() {
  7. television.playCctv2();
  8. }
  9. }
  1. public class CCTV3Command extends Command {
  2. public CCTV3Command(Television television) {
  3. super(television);
  4. }
  5. @Override
  6. public void execute() {
  7. television.playCctv3();
  8. }
  9. }
  1. public class CCTV4Command extends Command {
  2. public CCTV4Command(Television television) {
  3. super(television);
  4. }
  5. @Override
  6. public void execute() {
  7. television.playCctv4();
  8. }
  9. }
  1. public class CCTV5Command extends Command {
  2. public CCTV5Command(Television television) {
  3. super(television);
  4. }
  5. @Override
  6. public void execute() {
  7. television.playCctv5();
  8. }
  9. }

Client

  1. public class Client {
  2. public static void main(String[] args) {
  3. //创建一个电视机
  4. Television tv = new Television();
  5. //创建一个遥控器
  6. Invoker invoker = new Invoker();
  7. invoker.switchCommand(new CCTV1Command(tv));
  8. invoker.switchCommand(new CCTV2Command(tv));
  9. invoker.switchCommand(new CCTV3Command(tv));
  10. invoker.switchCommand(new CCTV4Command(tv));
  11. invoker.switchCommand(new CCTV5Command(tv));
  12. System.out.println("------undo--------");
  13. //模拟遥控器返回键
  14. invoker.undo();
  15. invoker.undo();
  16. invoker.undo();
  17. invoker.undo();
  18. System.out.println("------redo--------");
  19. invoker.redo();
  20. invoker.redo();
  21. invoker.redo();
  22. invoker.redo();
  23. }
  24. }

输出

  1. --CCTV1--
  2. --CCTV2--
  3. --CCTV3--
  4. --CCTV4--
  5. --CCTV5--
  6. ------undo--------
  7. --CCTV4--
  8. --CCTV3--
  9. --CCTV2--
  10. --CCTV1--
  11. ------redo--------
  12. --CCTV2--
  13. --CCTV3--
  14. --CCTV4--
  15. --CCTV5--

技术要点总结

  • Command 接口非常简单,通常只有一个execute方法,如果要支持撤销操作的话,再加一个unexecute方法,实现数据回滚等。
  • 每个具体的命令类内部封装了实际执行命令的那个类(Recevier),或者那些类,以及执行需要的数据,上面的示例主要是所有的命令类都是操作同一个对象,否则下沉至命令实现类持有。
  • 每个具体命令类只完成一个请求,有多少个请求就有多少个命令。
  • Invoker类只认识接口Command,其他的都不认识
  • 客户端类负责生成命令,并通过Invoker组装执行。