hawtim.github.io
hawtim.github.io copied to clipboard
行为型模式-命令模式
命令模式是一种行为型模式。把对象之间的请求封装在命令对象中,将命令的调用者和命令的接收者完全解耦。
行为
当调用命令的 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)操作。
- 程序需要将一组操作组合在一起,即支持宏命令。
比如 编辑器的实现、命令行的实现等等。
总结
- 命令模式是一种行为型模式,别名 动作模式 / 事务模式
- 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
- 命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。