6_命令模式
1 命令模式的用途
- 应用场景: 有时需要向某些对象发送请求, 但是并不知道请求的接收者是谁, 也不知道被请求的操作是什么
此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系
订餐
- 命令模式将客人订餐的请求(订单)封装成
command
对象 - 订单从服务员手中传递给厨师
客人不需要知道厨师是谁 -- 解耦
客人可以预定订单 -- 异步
2 命令模式的例子 -- 菜单程序
2.1 前置条件
- A实现按钮批量绘制
- 其他人实现按钮点击的具体行为
思考:A在绘制完成之后,应该如何让B为按钮绑定onclick事件
2.2 实现
主题:将不变的事物和变化的事物分离开
按下按钮之后会发生一些事情是不变的,具体会发生什么事情是可变的
1. 绘制按钮
let btn1 = document.getElementById('button1')
let btn2 = document.getElementById('button2')
let btn3 = document.getElementById('button3')
2. 往按钮上安装命令 -- setCommand函数
约定:command.execute()
-- 执行命令的动作
即使不知道这些命令具体代表什么操作,但只需要预留好安装命令的接口
function setCommand(btn, command) {
btn.onclick = () => command.execute()
}
3. 定义具体行为
let MenuBar = {
refresh() {
console.log('刷新菜单目录')
}
}
let SubMenu = {
add() {
console.log('增加子菜单')
},
del() {
console.log('删除子菜单')
}
}
4. 封装行为的命令类
class RefreshMenuBarCommand {
constructor(receiver) {
this.receiver = receiver
}
execute() {
this.receiver.refresh()
}
}
class AddSubMenuCommand {
constructor(receiver) {
this.receiver = receiver
}
execute() {
this.receiver.add()
}
}
class DelSubMenuCommand {
constructor(receiver) {
this.receiver = receiver
}
execute() {
this.receiver.del()
}
}
5. 将具体行为传入命令类
let refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar)
let addSubMenuCommand = new AddSubMenuCommand(SubMenu)
let delSubMenuCommand = new DelSubMenuCommand(SubMenu)
6. 将command对象安装到button上
setCommand(btn1, refreshMenuBarCommand)
setCommand(btn2, addSubMenuCommand)
setCommand(btn3, delSubMenuCommand)
3 js中的命令模式
3.1 简单实现绑定
function bindClick(btn, func) {
btn.onclick = func
}
bindClick(btn1, MenuBar.refresh)
bindClick(btn2, SubMenu.add)
bindClick(btn3, SubMenu.del)
命令模式的接收者被当成
command
对象的属性保存起来,同时约定执行命令的操作调用command.execute
方法
在使用闭包的命令模式实现中,接收者被封闭在闭包产生的环境中,执行命令的操作可以更加简单,仅仅执行回调函数即可
3.2 闭包实现命令模式
function setCommand(btn, func) {
btn.onclick = () => {
func()
}
}
function RefreshMenuBarCommand(receiver) {
return () => {
receiver.refresh()
}
}
let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(btn1, refreshMenuBarCommand)
- 除执行命令外,可能还有撤销命令等操作
3.3 闭包中调用 execute
方法
将执行函数改为调用 execute
方法
function setCommand(btn, command) {
btn.onclick = () => {
command.execute()
}
}
function RefreshMenuBarCommand(receiver) {
return {
execute() {
receiver.refresh()
}
}
}
let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar)
setCommand(btn1, refreshMenuBarCommand)
4 撤销命令
4.1 前置条件
input+btn
点击A按钮小球实现输入数值的水平位移,点击B按钮让小球位置复原
4.2 简单实现
let ball = document.getElementById('ball')
let pos = document.getElementById('pos')
let moveBtn = document.getElementById('moveBtn')
moveBtn.onclick = function() {
let animate = new Animate(ball)
animate.start('left', pos.value, 1000, 'strongEaseOut')
};
4.3 使用命令模式重写小球位移
class MoveCommand {
constructor(receiver, pos) {
this.receiver = receiver
this.pos = pos
}
execute() {
this.receiver.start(
'left', this.pos, 1000, 'strongEaseOut'
)
}
}
let moveCommand
moveBtn.onclick = () => {
let animate = new Animate(ball)
moveCommand = new MoveCommand(animate, pos.value)
moveCommand.execute()
}
4.4 点击B按钮让小球位置复原
class MoveCommand {
constructor(receiver, pos) {
this.receiver = receiver
this.pos = pos
this.oldPos = null
}
execute() {
this.receiver.start(
'left', this.pos, 1000, 'strongEaseOut'
)
this.oldPos
= this.receiver.dom.getBoundingClientRect()[
this.receiver.propertyName
]
}
undo() {
this.receiver.start(
'left', this.oldPos, 1000, 'strongEaseOut'
)
}
}
let moveCommand
let animate = new Animate(ball)
moveBtn.onclick = () => {
moveCommand = new MoveCommand(animate, pos.value)
moveCommand.execute()
}
cancelBtn.onclick = () => {
console.log('moveCommand', moveCommand);
moveCommand.undo()
}
5 撤消和重做
5.1 前置条件
- 利用堆栈存储执行过的命令
播放游戏录屏功能实现
2. 实现命令播放
let Ryu = {
attack() {
console.log('攻击')
},
defense() {
console.log('防御')
},
jump() {
console.log('跳跃')
},
crouch() {
console.log('蹲下')
}
}
function makeCommand(receiver, state) {
return () => {
receiver[state]()
}
}
let commands = {
"119": "jump", // W
"115": "crouch", // S
"97": "defense", // A
"100": "attack" // D
}
let commandStack = [] // 保存命令的堆栈
document.onkeypress = ev => {
let keyCode = ev.keyCode,
command = makeCommand(Ryu, commands[keyCode])
if(command) {
// 执行命令
command()
// 记录执行过的命令
commandStack.push(command)
}
}
document.getElementById('replay').onclick = () => {
let command
while(command = commandStack.shift()) {
command()
}
}
6 命令队列
- 等待当前命令执行时,将后续命令对象压入队列
- 当前
command
对象的职责完成后,主动通知队列,依次执行队列中的命令对象
7 宏命令
宏命令是一组命令的集合,通过执行宏命令的方式,可以一次执行一批命令
如何逐步创建一个宏命令
7.1 创建好各种 Command
let closeDoorCommand = {
execute() {
console.log('关门');
}
}
let openPcCommand = {
execute() {
console.log('开电脑');
}
}
let openQQCommand = {
execute() {
console.log('登录QQ');
}
}
7.2 定义宏命令
class MacroCommand {
constructor() {
this.commandsList = []
}
add(command) {
this.commandsList.push(command)
}
execute() {
this.commandsList.map(command => command.execute())
}
}
7.3 应用
let macroCommand = new MacroCommand()
macroCommand.add(closeDoorCommand)
macroCommand.add(openPcCommand)
macroCommand.add(openQQCommand)
macroCommand.execute()