Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

行为型模式-命令模式 #43

Open
hawtim opened this issue Oct 9, 2020 · 1 comment
Open

行为型模式-命令模式 #43

hawtim opened this issue Oct 9, 2020 · 1 comment

Comments

@hawtim
Copy link
Owner

hawtim commented Oct 9, 2020

命令模式是一种行为型模式。把对象之间的请求封装在命令对象中,将命令的调用者和命令的接收者完全解耦。

行为

当调用命令的 execute 方法时,不同的命令会做不同的事情,从而阐释不同的执行结果,此外还有 undo 和 redo 等操作。

电视遥控器例子

我们直接从代码入手,依次注释 命令的接收者、命令对象、命令的调用者

document.write(`<body>
<button id="execute">execute</button>
<button id="undo">undo</button>
</body>`)
// 命令的接收者
let Tv = {
  open() {
    console.log('open the tv')
  },
  close() {
    console.log('close the tv')
  }
}
// 命令对象
class OpenTvCommand {
  constructor(receiver) {
    this.receiver = receiver
  }
  // 行为 => 请求
  execute() {
    this.receiver.open()
  }
  undo() {
    this.receiver.undo()
  }
}
// 命令的调用者
let setCommand = (command) => {
  document.getElementById('execute').onclick = () => {
    command.execute()
  }
  document.getElementById('undo').onclick = () => {
    command.undo()
  }
}
// 实例化命令并解耦
setCommand(new OpenTvCommand(Tv))

上述的简单的例子可以直观的理解命令模式的原理。

我们接下看另外一个例子,通过控制小球的移动,本质的模式是相同的,只是增加了更多的逻辑,更接近我们的业务代码。

一个控制小球移动的例子

// 补充 Animate 类
let tween = {
  linear(t, b, c, d) {
    return c * t / d + b
  },
  easeIn(t, b, c, d) {
    return c * (t /= d) * t + b
  }
  // ...
}

class Animate {
  constructor(dom) {
    this.dom = dom // 进行运动的dom节点
    this.startTime = 0 // 动画开始时间
    this.endTime = 0
    this.startPos = 0 // 动画开始时,dom节点的位置,即dom的初始位置
    this.endPos = 0 // 动画开始时,dom节点的位置,即dom的目标位置
    this.propertyName = null // dom 节点需要被改变的 CSS 的属性名
    this.easing = null // 缓动算法
    this.duration = null // 动画持续时间
  }
  start(propertyName, endPos, duration, easing) {
    this.startTime = +new Date
    this.startPos = this.dom.getBoundingClientRect()[propertyName]
    this.propertyName = propertyName
    this.endPos = endPos
    this.duration = duration
    this.easing = tween[easing]
    let timeId = setInterval(() => {
      if (this.step() === false) {
        clearInterval(timeId)
      }
    }, 19)
  }
  step() {
    let t = +new Date
    if (t >= this.startTime + this.duration) {
      this.update(this.endPos)
      return false
    }
    let pos = this.easing(t - this.startTime, this.startPos, this.endPos - this.startPos, this.duration)
    this.update(pos)
  }
  update(pos) {
    this.dom.style[this.propertyName] = `${pos}px`
  }
}

document.write(`<body>
<div id="ball" style="position: absolute;top: 50px; left: 0; background: #000; width: 50px; height: 50px;"></div>
输入小球移动后的位置: <input id="pos"/>
<button id="moveBtn">开始移动</button>
<button id="cancelBtn">撤销移动</button>
</body>`)

let ball = document.getElementById('ball')
let pos = document.getElementById('pos')
let moveBtn = document.getElementById('moveBtn')
let cancelBtn = document.getElementById('cancelBtn')

// 控制小球的命令对象
class MoveCommand {
  constructor(receiver, pos) {
    this.receiver = receiver
    this.pos = pos
    this.oldPos = null
  }
  execute() {
    this.receiver.start('left', this.pos, 1000, 'linear')
    this.oldPos = this.receiver.dom.getBoundingClientRect()[this.receiver.propertyName]
  }
  undo() {
    this.receiver.start('left', this.oldPos, 1000, 'linear')
  }
}

var moveCommand

moveBtn.onclick = () => {
  var animate = new Animate(ball)
  moveCommand = new MoveCommand(animate, pos.value)
  moveCommand.execute()
}

cancelBtn.onclick = () => {
  moveCommand.undo()
}

优点

  • 降低程序的耦合度
  • 新的命令可以很容易的加入程序中
  • 可以设计一个命令队列和宏命令(组合命令)(参考组合命令)
  • 可以方便实现对请求的 undo 和 redo

缺点

  • 可能会导致程序中有过多的命令类

使用场景

  • 程序需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 程序需要在不同的时间指定请求、将请求排队和执行请求。
  • 程序需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  • 程序需要将一组操作组合在一起,即支持宏命令。

比如 编辑器的实现、命令行的实现等等。

总结

  • 命令模式是一种行为型模式,别名 动作模式 / 事务模式
  • 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
  • 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
@hawtim
Copy link
Owner Author

hawtim commented Oct 9, 2020

参考文档

命令模式

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant