命令模式

什么是命令模式?

从这样一个场景切入:我们在手机上点外卖时,我们不知道那个厨师会给我做饭,但我们能知道会有人处理这个订单,并且按照订单上的要求制作饭菜。在这个过程中,客户和厨师之间没有直接联系。而是通过 订单——这种命令方式 来间接沟通。

image

在上面这个场景中,手机订单就是命令对象,它封装了客户的需求,并且传递给厨师;厨师就是这个请求的接收者,负责根据命令对象中的请求进行处理。由于客户和厨师之间并不能直接沟通,而是通过命令对象间接沟通,所以命令模式使得请求者和接收者解耦合,提高了系统的灵活性。

定义:命令模式是一种将请求封装成对象,以便不同的请求、队列或日志来参数化其他对象。该模式允许请求的发送者和接受者解耦,而无需指定接收者。在命令模式中,请求是由命令对象表示,该对象知道如何实现请求。接收者知道如何处理请求。

命令模式的使用场景有哪些 ?

当我们前后端同时开发的时候,通常情况下我们都是先约定好数据传输格式。而这个约定的数据传输格式,也可看成是一个命令。我们不需要关系后端是如何处理的这个数据。只需要遵循命令格式即可完成相应的功能。这样也就体现出命令模式,使得前后端解耦合的特点。

我们在玩CSGO的时候,就存在命令模式。当我们被击杀的时候回放画面,就利用的命令模式的生命周期很长的特点。其实本质上回放就是将该阶段的命令重新执行了一遍。

简单实现命令模式

控制小球移动

在这个案例中我们需要移动小球和撤销移动。代码实现如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>命令模式控制小球</title>
  <style>
    #ball {
      position: absolute;
      background-color: #000;
      width: 50px;
      height: 50px;
      left: 0;
      top: 100px;
      transition: all 1s ease;
      border-radius: 50%;
    }
  </style>
</head>
<body>
    <div id="ball">

    </div>
    
    <button id="moveBtn">开始移动</button>
    <button id="undo">撤销</button>
    输入小球移动后的位置:<input type="number" id="pos"/>
    <script>
      
      
      // 命令接收者。
      class Animate {
        constructor(el) {
          this.el = el;
        }

        setPostion(direct, d) {
          let currentPos = this.el.style[direct];
          if (currentPos === "") {
            currentPos = 0;
          } else {
            currentPos = currentPos.slice(0, -2);
          }
          currentPos = Number.parseInt(currentPos);
          this.el.style[direct] = `${currentPos + d}px`;
        }

        getController() {
          return {
            moveInX: (d) => this.setPostion("left", d),
            moveInY: (d) => this.setPostion("top", d)
          };
        }
      }
      
      // 命令对象
      class MoveCommand {
        constructor(receiver, moveMethod, d) {
          this.receiver = receiver;
          this.moveMethod = moveMethod;
          this.d = d;
          this.oldDis = [];
        }

        execute() {
          this.oldDis.push([this.moveMethod, this.d]);
          const moveController = this.receiver.getController();
          moveController[this.moveMethod](this.d);
        }

        undo() {
          if (this.oldDis.length === 0)
            return;
          const [moveMethod, d] = this.oldDis.pop();
          const moveController = this.receiver.getController();
          moveController[moveMethod](-d);
        }
      }
      
      const ball = document.querySelector("#ball");
      const moveBtn = document.querySelector("#moveBtn");
      const pos = document.querySelector("#pos");
      const undo = document.querySelector("#undo");

      
      
      
      
      const oldCommandQueue = [];
      const waitCommandQueue = [];
      
      let lock = false;
      moveBtn.onclick = function() {
        const animate = new Animate(ball);
        // 传入接受者和数据到命令对对象中。
        // 命令对象保存了接受者处理逻辑
        const moveCommand = new MoveCommand(animate, "moveInX", Number.parseInt(pos.value));
        // 执行命令对象的处理逻辑
        moveCommand.execute();
        // 将执行完后的命令对象保存,方便执撤销
        oldCommandQueue.push(moveCommand);
      };

      undo.onclick = function() {
        if (oldCommandQueue.length === 0)
          return;
        // 撤销命令对象那个
        const moveCommend = oldCommandQueue.pop();
        moveCommend.undo();
      }

    </script>
</body>
</html>

上面的代码中,我们通过命令对象封装了处理逻辑。而接收者可以任意传递。同时,命令模式可以很方便执行撤销。可以运行上面代码试试。