0%

设计模式之行为型模式(二):命令模式

1. 命名模式概述

  • 概念

    • 将一个请求封装为一个对象,从而使可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作
    • 命令模式可以用于请求排队:只需创建一个命令队列,将每个需要执行的命令一次传入队列中,然后工作线程不断地从命令队列中取出队列头的命令,再执行命令即可。实际上,Android app 的界面就是这么实现的
  • 优点

    • 降低系统的耦合度。将“行为请求者”和“行为实现者”解耦
    • 扩展性强。增加或删除命令非常方便,并且不会影响其他类
    • 封装“方法调用”,方便实现 Undo 和 Redo 操作
    • 灵活性强,可以实现宏命令(组合多个命令形成的宏大的命令)
  • 缺点

    • 会产生大量的命令类,增加了系统的复杂性。设计模式的工作常常是将功能细化,很容易造成类膨胀,在命令模式中体现得尤其明显
    • 实际上使用设计模式需要慎重,在确实有必要的情况下再使用设计模式。作为一名合格的程序员,心中始终要将产品放在第一位,代码的易读性比炫技更重要

2. 命令模式 Demo

  • 大门类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Door {
    public void openDoor() {
    System.out.println("门打开了");
    }

    public void closeDoor() {
    System.out.println("门关闭了");
    }
    }
  • 电灯类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Light {
    public void lightOn() {
    System.out.println("打开了电灯");
    }

    public void lightOff() {
    System.out.println("关闭了电灯");
    }
    }
  • 电视类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Tv {
    public void TurnOnTv() {
    System.out.println("电视打开了");
    }

    public void TurnOffTv() {
    System.out.println("电视关闭了");
    }
    }
  • 音乐类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Music {
    public void play() {
    System.out.println("开始播放音乐");
    }

    public void stop() {
    System.out.println("停止播放音乐");
    }
    }
  1. 万能遥控器 1.0:功能单一

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    // 初始化开关
    Switch switchDoor = 省略绑定UI代码;
    Switch switchLight = 省略绑定UI代码;
    Switch switchTv = 省略绑定UI代码;
    Switch switchMusic = 省略绑定UI代码;

    // 初始化智能家居
    Door door = new Door();
    Light light = new Light();
    Tv tv = new Tv();
    Music music = new Music();

    // 大门开关遥控
    switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
    if (isChecked) {
    door.openDoor();
    } else {
    door.closeDoor();
    }
    });
    // 电灯开关遥控
    switchLight.setOnCheckedChangeListener((view, isChecked) -> {
    if (isChecked) {
    light.lightOn();
    } else {
    light.lightOff();
    }
    });
    // 电视开关遥控
    switchTv.setOnCheckedChangeListener((view, isChecked) -> {
    if (isChecked) {
    tv.TurnOnTv();
    } else {
    tv.TurnOffTv();
    }
    });
    // 音乐开关遥控
    switchMusic.setOnCheckedChangeListener((view, isChecked) -> {
    if (isChecked) {
    music.play();
    } else {
    music.stop();
    }
    });
  2. 万能遥控器 2.0:增加撤销功能

    • 设计一个枚举类 Operation,代表每一步的操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public enum Operation {
      DOOR_OPEN,
      DOOR_CLOSE,
      LIGHT_ON,
      LIGHT_OFF,
      TV_TURN_ON,
      TV_TURN_OFF,
      MUSIC_PLAY,
      MUSIC_STOP
      }
    • 在客户端定义枚举类型变量 lastOperation,实现撤销一步操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      public class Client {

      // 上一步的操作
      Operation lastOperation;

      @Test
      protected void test() {

      // 初始化开关和撤销按钮
      Switch switchDoor = 省略绑定UI代码;
      Switch switchLight = 省略绑定UI代码;
      Switch switchTv = 省略绑定UI代码;
      Switch switchMusic = 省略绑定UI代码;
      Button btnUndo = 省略绑定UI代码;

      // 初始化智能家居
      Door door = new Door();
      Light light = new Light();
      Tv tv = new Tv();
      Music music = new Music();

      // 大门开关遥控
      switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
      if (isChecked) {
      lastOperation = Operation.DOOR_OPEN;
      door.openDoor();
      } else {
      lastOperation = Operation.DOOR_CLOSE;
      door.closeDoor();
      }
      });

      // 电灯开关遥控
      switchLight.setOnCheckedChangeListener((view, isChecked) -> {
      if (isChecked) {
      lastOperation = Operation.LIGHT_ON;
      light.lightOn();
      } else {
      lastOperation = Operation.LIGHT_OFF;
      light.lightOff();
      }
      });

      ... 电视、音乐类似

      btnUndo.setOnClickListener(view -> {
      if (lastOperation == null) return;
      // 撤销上一步
      switch (lastOperation) {
      case DOOR_OPEN:
      door.closeDoor();
      break;
      case DOOR_CLOSE:
      door.openDoor();
      break;
      case LIGHT_ON:
      light.lightOff();
      break;
      case LIGHT_OFF:
      light.lightOn();
      break;
      ... 电视、音乐类似
      }
      });
      }
      }
    • 优化:使用栈数据结构实现撤销多步(缺点:代码越来越臃肿,可读性和可扩展性变差)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      public class Client {

      // 所有的操作
      Stack<Operation> operations = new Stack<>();

      @Test
      protected void test() {

      // 初始化开关和撤销按钮
      Switch switchDoor = 省略绑定UI代码;
      Switch switchLight = 省略绑定UI代码;
      Switch switchTv = 省略绑定UI代码;
      Switch switchMusic = 省略绑定UI代码;
      Button btnUndo = 省略绑定UI代码;

      // 初始化智能家居
      Door door = new Door();
      Light light = new Light();
      Tv tv = new Tv();
      Music music = new Music();

      // 大门开关遥控
      switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
      if (isChecked) {
      operations.push(Operation.DOOR_OPEN);
      door.openDoor();
      } else {
      operations.push(Operation.DOOR_CLOSE);
      door.closeDoor();
      }
      });

      // 电灯开关遥控
      switchLight.setOnCheckedChangeListener((view, isChecked) -> {
      if (isChecked) {
      operations.push(Operation.LIGHT_ON);
      light.lightOn();
      } else {
      operations.push(Operation.LIGHT_OFF);
      light.lightOff();
      }
      });

      ...电视、音乐类似

      // 撤销按钮
      btnUndo.setOnClickListener(view -> {
      if (operations.isEmpty()) return;
      // 弹出栈顶的上一步操作
      Operation lastOperation = operations.pop();
      // 撤销上一步
      switch (lastOperation) {
      case DOOR_OPEN:
      door.closeDoor();
      break;
      case DOOR_CLOSE:
      door.openDoor();
      break;
      case LIGHT_ON:
      light.lightOff();
      break;
      case LIGHT_OFF:
      light.lightOn();
      break;
      ...电视、音乐类似
      }
      });
      }
      }
  3. 万能遥控器 3.0命令模式,让遥控器不需要知道家居的接口,它只需要负责监听用户按下开关,再根据开关状态发出正确的命令,对应的家居在收到命名后做出响应,达到将“行为请求者”和“行为实现者” 解耦的目的

    • 定义一个命令接口

      1
      2
      3
      public interface ICommand {
      void execute();
      }
    • 开门命令

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class DoorOpenCommand implements ICommand {
      private Door door;

      public void setDoor(Door door) {
      this.door = door;
      }

      @Override
      public void execute() {
      door.openDoor();
      }
      }
    • 关门命令

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class DoorCloseCommand implements ICommand {
      private Door door;

      public void setDoor(Door door) {
      this.door = door;
      }


      @Override
      public void execute() {
      door.closeDoor();
      }
      }
    • 开灯命令

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class LightOnCommand implements ICommand {

      Light light;

      public void setLight(Light light) {
      this.light = light;
      }

      @Override
      public void execute() {
      light.lightOn();
      }
      }
    • 关灯命令

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public class LightOffCommand implements ICommand {

      Light light;

      public void setLight(Light light) {
      this.light = light;
      }

      @Override
      public void execute() {
      light.lightOff();
      }
      }
    • 客户端:将家居控制的代码转移到了命令类中,当命令执行时,调用对应家具的 API 实现开启或关闭

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      // 初始化命令
      DoorOpenCommand doorOpenCommand = new DoorOpenCommand();
      DoorCloseCommand doorCloseCommand = new DoorCloseCommand();
      doorOpenCommand.setDoor(door);
      doorCloseCommand.setDoor(door);
      LightOnCommand lightOnCommand = new LightOnCommand();
      LightOffCommand lightOffCommand = new LightOffCommand();
      lightOnCommand.setLight(light);
      lightOffCommand.setLight(light);
      ...电视、音乐类似

      // 大门开关遥控
      switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
      if (isChecked) {
      doorOpenCommand.execute();
      } else {
      doorCloseCommand.execute();
      }
      });
      // 电灯开关遥控
      switchLight.setOnCheckedChangeListener((view, isChecked) -> {
      if (isChecked) {
      lightOnCommand.execute();
      } else {
      lightOffCommand.execute();
      }
      });
      ...电视、音乐类似
    • 客户端优化:由于每个命令都被抽象成了同一个接口,可以将开关代码统一起来

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      public class Client {

      @Test
      protected void test() {
      ...初始化

      // 大门开关遥控
      switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, doorOpenCommand, doorCloseCommand);
      });
      // 电灯开关遥控
      switchLight.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, lightOnCommand, lightOffCommand);
      });
      // 电视开关遥控
      switchTv.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, turnOnTvCommand, turnOffTvCommand);
      });
      // 音乐开关遥控
      switchMusic.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, musicPlayCommand, musicStopCommand);
      });
      }

      private void handleCommand(boolean isChecked, ICommand openCommand, ICommand closeCommand) {
      if (isChecked) {
      openCommand.execute();
      } else {
      closeCommand.execute();
      }
      }
      }
    • 使用命令模式实现撤销功能:在命令接口中新增 undo 方法

      1
      2
      3
      4
      5
      6
      public interface ICommand {

      void execute();

      void undo();
      }
    • 开门命令中新增 undo

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class DoorOpenCommand implements ICommand {
      private Door door;

      public void setDoor(Door door) {
      this.door = door;
      }

      @Override
      public void execute() {
      door.openDoor();
      }

      @Override
      public void undo() {
      door.closeDoor();
      }
      }
    • 关门命令中新增 undo

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class DoorCloseCommand implements ICommand {
      private Door door;

      public void setDoor(Door door) {
      this.door = door;
      }

      @Override
      public void execute() {
      door.closeDoor();
      }

      @Override
      public void undo() {
      door.openDoor();
      }
      }
    • 开灯命令中新增 undo

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class LightOnCommand implements ICommand {

      Light light;

      public void setLight(Light light) {
      this.light = light;
      }

      @Override
      public void execute() {
      light.lightOn();
      }

      @Override
      public void undo() {
      light.lightOff();
      }
      }
    • 关灯命令中农新增 undo

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public class LightOffCommand implements ICommand {

      Light light;

      public void setLight(Light light) {
      this.light = light;
      }

      @Override
      public void execute() {
      light.lightOff();
      }

      @Override
      public void undo() {
      light.lightOn();
      }
      }
    • 客户端

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      public class Client {

      // 所有的命令
      Stack<ICommand> commands = new Stack<>();

      @Test
      protected void test() {
      ...初始化

      // 大门开关遥控
      switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, doorOpenCommand, doorCloseCommand);
      });
      // 电灯开关遥控
      switchLight.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, lightOnCommand, lightOffCommand);
      });
      // 电视开关遥控
      switchTv.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, turnOnTvCommand, turnOffTvCommand);
      });
      // 音乐开关遥控
      switchMusic.setOnCheckedChangeListener((view, isChecked) -> {
      handleCommand(isChecked, musicPlayCommand, musicStopCommand);
      });

      // 撤销按钮
      btnUndo.setOnClickListener(view -> {
      if (commands.isEmpty()) return;
      // 撤销上一个命令
      ICommand lastCommand = commands.pop();
      lastCommand.undo();
      });
      }

      private void handleCommand(boolean isChecked, ICommand openCommand, ICommand closeCommand) {
      if (isChecked) {
      commands.push(openCommand);
      openCommand.execute();
      } else {
      commands.push(closeCommand);
      closeCommand.execute();
      }
      }
      }
    • 使用宏命令:给遥控器添加一个“睡眠”按钮,按下时可以一键执行关闭大门、关闭电灯、关闭电视、打开音乐(听着音乐睡觉,优雅)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      public class MacroCommand implements ICommand {
      // 定义一组命令
      List<ICommand> commands;

      public MacroCommand(List<ICommand> commands) {
      this.commands = commands;
      }

      @Override
      public void execute() {
      // 宏命令执行时,每个命令依次执行
      for (int i = 0; i < commands.size(); i++) {
      commands.get(i).execute();
      }
      }

      @Override
      public void undo() {
      // 宏命令撤销时,每个命令依次撤销
      for (int i = 0; i < commands.size(); i++) {
      commands.get(i).undo();
      }
      }
      }
    • 使用宏命令的客户端:可以任意组合多个命令,并且完全不会增加程序结构的复杂度

      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 定义睡眠宏命令
      MacroCommand sleepCommand = new MacroCommand(Arrays.asList(doorCloseCommand, lightOffCommand, turnOffTvCommand, musicPlayCommand));
      // 睡眠按钮
      btnSleep.setOnClickListener(view -> {
      // 将执行的命令保存到栈中,以便撤销
      commands.push(sleepCommand);
      // 执行睡眠命令
      sleepCommand.execute();
      });
-------------------- 本文结束感谢您的阅读 --------------------