详解Javascript实践中的命令模式


Posted in Javascript onMay 05, 2021

定义

Encapsulate a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests,and support undoable operations.“

「命令模式」将「请求」封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时支持可撤消的操作。

这里的「请求」的定义,并不是我们前端常说的「Ajax 请求」,而是一个「动作请求」,也就是发起一个行为。例如,通过遥控器关闭电视,这里的「关闭」就是一个请求。在命令模式中,我们将请求抽象成一个命令,这个命令是可复用的,它只关心它的接受者(电视);而对于动作的发起者(遥控器)来说,它只关心它所支持的命令有哪些,而不关心这些命令具体是做什么的。

结构

命令模式的类图如下:

详解Javascript实践中的命令模式

在该类图中,我们看到五个角色:

  • Client - 创建 Concrete Command 与 Receiver(应用层)。
  • Invoker - 命令的发出者,通常会持有命令对象,可以持有很多的命令对象。
  • Receiver - 命令接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • Command - 命令接口。
  • ConcreteCommand - 命令接口的实现。

Reciver 与 Invoker 没有耦合,当需要拓展功能时,通过新增 Command,因此命令模式符合开闭原则。

实例

自定义快捷键

自定义快捷键是一个编辑器的最基本功能。通过命令模式,我们可以写出一个将键位与键位逻辑解耦的结构。

interface Command {
    exec():void
}

type Keymap = { [key:string]: Command }
class Hotkey {
    keymap: Keymap = {}

    constructor(keymap: Keymap) {
        this.keymap = keymap
    }

    call(e: KeyboardEvent) {
        const prefix = e.ctrlKey ? 'ctrl+' : ''
        const key = prefix + e.key
        this.dispatch(key)
    }

    dispatch(key: string) {
        this.keymap[key].exec()
    }
}

class CopyCommand implements Command {
    constructor(clipboard: any) {}
    exec() {}
}

class CutCommand implements Command {
    constructor(clipboard: any) {}
    exec() {}
}

class PasteCommand implements Command {
    constructor(clipboard: any) {}
    exec() {}
}

const clipboard = { data: '' }
const keymap = {
    'ctrl+x': new CutCommand(clipboard),
    'ctrl+c': new CopyCommand(clipboard),
    'ctrl+v': new PasteCommand(clipboard)
}
const hotkey = new Hotkey(keymap)

document.onkeydown = (e) => {
    hotkey.call(e)
}

在本例中,hotkey是 Invoker,clipboard是 Receiver。当我们需要修改已有的 keymap 时,只需要新增或替换已有的key或Command即可。

是不是觉得这个写法似曾相识?没错Redux 也是应用了命令模式,Store 相当于 Receiver,Action 相当于 Command,Dispatch 相当于 Invoker。

撤销与重做

基于命令模式,我们可以很容易拓展,使它支持撤销与重做。

interface IPerson {
    moveTo(x: number, y: number): void
}

class Person implements Person {
    x = 0
    y = 0

    moveTo(x: number, y: number) {
        this.x = x
        this.y = y
    }
}

interface Command {
    exec(): void
    undo(): void
}

class MoveCommand implements Command {
    prevX = 0
    prevY = 0

    person: Person

    constructor(person: Person) {
        this.person = person
    }

    exec() {
        this.prevX = this.person.x
        this.prevY = this.person.y
        this.person.moveTo(this.prevX++, this.prevY++)
    }

    undo() {
        this.person.moveTo(this.prevX, this.prevY)
    }
}


const ezio = new Person()
const moveCommand = new MoveCommand(ezio)
moveCommand.exec()
console.log(ezio.x, ezio.y)
moveCommand.undo()
console.log(ezio.x, ezio.y)

录制与回放

想想我们在游戏中的录制与回放功能,如果将角色的每个动作都作为一个命令的话,那么在录制时就能够得到一连串的命令队列。

class Control {
    commands: Command[] = []
    
    exec(command) {
        this.commands.push(command)
        command.exec(this.person)
    }
}

const ezio = new Person()
const control = new Control()
control.exec(new MoveCommand(ezio))
control.exec(new MoveCommand(ezio))

console.log(control.commands)

当我们有了命令队列,我们又能够很容易得进行多次的撤销和重做,实现一个命令的历史记录。只需要移动当前命令队列的指针即可。

class CommandHistory {
    commands: Command[] = []
    
    index = 0
    
    get currentCommand() {
        return this.commands[index]
    }
    
    constructor(commands: Command[]) {
        this.commands = commands
    }
    
    redo() {
        this.index++
        this.currentCommand.exec()
    }
    
    undo() {
        this.currentCommand.undo()
        this.index--
    }
}

同时,如果我们将命令序列化成一个对象,它便可以用于保存与传递。这样我们将它发送到远程计算机,就能实现远程控制ezio移动的功能。

[{
    type: 'move',
    x: 1,
    y: 1,
}, {
    type: 'move',
    x: 2,
    y: 2,
}]

宏命令

对Command进行一些简单的处理就能够将已有的命令组合起来执行,将其变成一个宏命令。

class BatchedCommand implements Command {
    commands = []
    
    constructor(commands) {
        this.commands = commands
    }
    
    exec() {
        this.commands.forEach(command => command.exec())
    }
}

const batchedMoveCommand = new BatchedCommand([
    new MoveCommand(ezio),
    new SitCommand(ezio),
])

batchedMoveCommand.exec()

总结

通过以上几个例子,我们可以看出命令模式有一下几个特点:

  • 低耦合,彻底消除了接受者与调用者之间的耦合。
  • 易拓展,只需要增加新的命令便可拓展出新功能。
  • 支持序列化,易于实现保存与传递。
  • 容易导致 Command 类庞大。

以上就是详解Javascript实践中的命令模式的详细内容,更多关于Javascript命令模式的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
动态载入/删除/更新外部 JavaScript/Css 文件的代码
Jul 03 Javascript
JavaScript中OnLoad几种使用方法
Dec 15 Javascript
必备的JS调试技巧汇总
Jul 20 Javascript
BootStrap使用file-input插件上传图片的方法
Sep 05 Javascript
Angularjs手动解析表达式($parse)
Oct 12 Javascript
微信小程序(应用号)开发新闻客户端实例
Oct 24 Javascript
详解javascript立即执行函数表达式IIFE
Feb 13 Javascript
javascript 通过键名获取键盘的keyCode方法
Dec 31 Javascript
vuejs项目打包之后的首屏加载优化及打包之后出现的问题
Apr 01 Javascript
Vue中的v-for循环key属性注意事项小结
Aug 12 Javascript
vue h5移动端禁止缩放代码
Oct 28 Javascript
关于Node.js中频繁修改代码重启服务器的问题
Oct 15 Javascript
如何制作自己的原生JavaScript路由
May 05 #Javascript
Vue项目中如何封装axios(统一管理http请求)
May 02 #Vue.js
如何用JavaScript学习算法复杂度
JS不要再到处使用绝对等于运算符了
Apr 30 #Javascript
如何用Node.js编写内存效率高的应用程序
用几道面试题来看JavaScript执行机制
Apr 30 #Javascript
详解前端任务构建利器Gulp.js使用指南
Apr 30 #Javascript
You might like
深入PHP获取随机数字和字母的方法详解
2013/06/06 PHP
PHP实现将视频转成MP4并获取视频预览图的方法
2015/03/12 PHP
利用php_imagick实现复古效果的方法
2016/10/18 PHP
Laravel使用PHPQRCODE实现生成带有LOGO的二维码图片功能示例
2017/07/07 PHP
PHP 实现人民币小写转换成大写的方法及大小写转换函数
2017/11/17 PHP
PHP PDOStatement::getColumnMeta讲解
2019/02/01 PHP
PHP中将一个字符串部分字符用星号*替代隐藏的实现代码
2019/09/08 PHP
js限制文本框为整数和货币的函数代码
2010/10/13 Javascript
jquery实现textarea输入字符控制(仿微博输入控制字符)
2013/04/26 Javascript
js实现右下角可关闭最小化div(可用于展示推荐内容)
2013/06/24 Javascript
转换字符串为json对象的方法详解
2013/11/29 Javascript
javascript 控制input只允许输入的各种指定内容
2014/06/19 Javascript
Bootstrap免费字体和图标网站(值得收藏)
2017/03/16 Javascript
Angularjs 双向绑定时字符串的转换成数字类型的问题
2017/06/12 Javascript
ajax+node+request爬取网络图片的实例(宅男福利)
2017/08/28 Javascript
详解Angular中通过$location获取地址栏的参数
2018/08/02 Javascript
ajax与jsonp的区别及用法
2018/10/16 Javascript
vue根据条件不同显示不同按钮的操作
2020/08/04 Javascript
[03:19]2016国际邀请赛中国区预选赛第四日TOP10镜头集锦
2016/07/01 DOTA
Python爬虫包 BeautifulSoup  递归抓取实例详解
2017/01/28 Python
python多进程实现进程间通信实例
2017/11/24 Python
python破解zip加密文件的方法
2018/05/31 Python
python操作openpyxl导出Excel 设置单元格格式及合并处理代码实例
2019/08/27 Python
利用python汇总统计多张Excel
2020/09/22 Python
python基于socket模拟实现ssh远程执行命令
2020/12/05 Python
乌克兰时尚鞋子和衣服购物网站:Born2be
2018/05/24 全球购物
static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
2015/02/22 面试题
综合测评自我鉴定
2013/10/08 职场文书
人力资源管理专业学生自我评价
2013/11/20 职场文书
2014年领导班子专项整治整改方案
2014/09/28 职场文书
婚前协议书标准版
2014/10/19 职场文书
2014年工程师工作总结
2014/11/25 职场文书
思想品德评语大全
2014/12/31 职场文书
python如何获取网络数据
2021/04/11 Python
「月刊Action」2022年5月号封面公开
2022/03/21 日漫
vue使用refs获取嵌套组件中的值过程
2022/03/31 Vue.js