Skip to content

命令模式(Command)

命令模式指的是一个执行某些特定事情的指令

个人理解:命令模式的代码很优雅,它不关心如何实现,实现的是谁,松开两个对象之间的耦合关系。即请求对象发出一条命令,给一个对象,对象帮你找请求的接受者。比如你要点菜,找服务员,你不需要知道谁帮你炒,你也不知道哪位厨师,你只需要向服务员下单即可,服务员去帮你找合适的厨师接收你的请求。

命令模式和策略模式其实早已经融于到 Javascript 这门语言中,只是使用的时候你并不知道这属于命令模式。

常用的场景

  • 需要向某个对象发送请求,但不知道请求的接受者是谁,也不知道被请求的操作是什么。用于消除耦合。

现在比如一个程序,按钮的绘制由某一个程序员来干,而另一个程序员负责编写点击按钮后的行为。

分析:对绘制的程序员来说,他完全不知道按钮未来会干什么,只知道会发生事情,那怎么给它绑定 onclick 事件呢?因此点击了按钮之后,必须向某些负责具体行为的对象发送请求,这些对象就是请求的接收者。

html
<button id="btn1">点击按钮1</button>
<button id="btn2">点击按钮2</button>
<button id="btn3">点击按钮3</button>

<script>
  const btn1 = document.getElementById('btn1')
  const btn2 = document.getElementById('btn2')
  const btn3 = document.getElementById('btn3')

  const setCommand = function (btn, command) {
    btn.onclick = function () {
      command.execute()
    }
  }

  const MenuBar = {
    refresh: () => {
      console.log('刷新菜单目录')
    }
  }

  const SubMenu = {
    add: () => {
      console.log('增加子菜单')
    },
    del: () => {
      console.log('删除子菜单')
    }
  }

  const RefreshMenuBarCommand = function (receiver) {
    this.receiver = receiver
  }

  RefreshMenuBarCommand.prototype.execute = function () {
    this.receiver.refresh()
  }

  const AddSubMenuCommand = function (receiver) {
    this.receiver = receiver
  }

  AddSubMenuCommand.prototype.execute = function () {
    this.receiver.add()
  }

  const DelSubMenuCommand = function (receiver) {
    this.receiver = receiver
  }

  DelSubMenuCommand.prototype.execute = function () {
    this.receiver.del()
  }

  const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar)
  const addSubMenuCommand = new AddSubMenuCommand(SubMenu)
  const delSubMenuCommand = new DelSubMenuCommand(SubMenu)
  // 设置命令
  setCommand(btn1, refreshMenuBarCommand)
  setCommand(btn2, addSubMenuCommand)
  setCommand(btn3, delSubMenuCommand)
</script>

看了之后是不是感觉好麻烦,当然,这是面向对象语言的实现方法。在 Javascript 函数可以自由传递,可简单多了。

javascript
const MenuBar = {
  refresh() {
    console.log('刷新')
  }
}

const RefreshMenuBarCommand = function(receiver) {
  return {
    execute: function() {
      receiver.refresh()
    }
  }
}

const setCommand = function(button, command) {
  button.onclick = function() {
    command.execute()
  }
}

const refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(button1, refreshMenuBarCommand)

命令模式还要一个好处就是可以轻易实现撤销、重播等功能。

:实现重播。按上下左右键,按重播的时候,重新执行你之前的操作。常用地方:比如象棋的悔棋。

html
<button id="replay">播放录像</button>

<script>
  const Ryu = {
    attack: function () {
      console.log('攻击')
    },
    defense: function () {
      console.log('防御')
    },
    jump: function () {
      console.log('跳跃')
    },
    crouch: function () {
      console.log('蹲下')
    }
  }

  const makeCommand = function (receiver, state) {
    return function () {
      receiver[state]()
    }
  }

  const commands = {
    119: 'jump',
    115: 'crouch',
    97: 'defense',
    100: 'attack'
  }

  const commandStack = []

  document.onkeypress = function (ev) {
    const keyCode = ev.keyCode;
    const command = makeCommand(Ryu, commands[keyCode])
    if (command) {
      command()
      commandStack.push(command)
    }
  }

  document.getElementById('replay').onclick = function () {
    let command
    while (command = commandStack.shift()) {
      command()
    }
  }
</script>

延伸

宏命令

宏命令是命令模式与组合模式的联用产物。

打个比方:现在的智能家居,我设置了电影模式,当我发出这一命令时,它会自动把窗帘拉上,灯光调暗,打开投影。

javascript
const closeCurtainCommand = {
  execute: function() {
    console.log('窗帘拉上')
  }
}

const closeLightsCommand = {
  execute: function() {
    console.log('调暗灯光')
  }
}

const openProjectorCommand = {
  execute: function() {
    console.log('打开投影')
  }
}

const MacroCommand = function() {
  return {
    commandsList: [],
    add: function(command) {
      this.commandsList.push(command)
    },
    execute: function() {
      for(let i = 0, command; command = this.commandsList[i++];) {
        command.execute()
      }
    }
  }
}

const macroCommand = MacroCommand()
macroCommand.add(closeCurtainCommand)
macroCommand.add(closeLightsCommand)
macroCommand.add(openProjectorCommand)
macroCommand.execute()

Released under the MIT License.