详解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利用初始化数据装配模版的实现代码
Nov 17 Javascript
jQuery中append、insertBefore、after与insertAfter的简单用法与注意事项
Apr 04 Javascript
js 验证密码强弱的小例子
Mar 21 Javascript
JS中判断null、undefined与NaN的方法
Mar 26 Javascript
前端轻量级MVC框架CanJS详解
Sep 26 Javascript
javascript时间排序算法实现活动秒杀倒计时效果
Jan 28 Javascript
jquery点击切换背景色的简单实例
Aug 25 Javascript
Angularjs实现控制器之间通信方式实例总结
Mar 27 Javascript
微信小程序开发之点击按钮退出小程序的实现方法
Apr 26 Javascript
TypeScript中使用getElementXXX()的示例代码
Sep 12 Javascript
jQuery实现消息弹出框效果
Dec 10 jQuery
webpack安装配置与常见使用过程详解(结合vue)
Jun 01 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 md5下16位和32位的实现代码
2008/04/09 PHP
关于php正则匹配汉字的方法介绍
2013/04/25 PHP
php使用标签替换的方式生成静态页面
2015/05/21 PHP
php实现XSS安全过滤的方法
2015/07/29 PHP
动态创建的表格单元格中的事件实现代码
2008/12/30 Javascript
js打印纸函数代码(递归)
2010/06/18 Javascript
jquery的Tooltip插件 qtip使用详细说明
2010/09/08 Javascript
js下将字符串当函数执行的方法
2011/07/13 Javascript
js+css 实现遮罩居中弹出层(随浏览器窗口滚动条滚动)
2013/12/11 Javascript
ext combobox动态加载数据库数据(附前后台)
2014/06/17 Javascript
EasyUI中datagrid在ie下reload失败解决方案
2015/03/09 Javascript
浅谈JSON.parse()和JSON.stringify()
2015/07/14 Javascript
js+css实现回到顶部按钮(back to top)
2016/03/02 Javascript
vue分页组件table-pagebar使用实例解析
2020/11/15 Javascript
js控制文本框只能输入中文、英文、数字与指定特殊符号的实现代码
2016/09/09 Javascript
微信小程序 网络API 上传、下载详解
2016/11/09 Javascript
BootStrap中jQuery插件Carousel实现轮播广告效果
2017/03/27 jQuery
彻底解决 webpack 打包文件体积过大问题
2017/07/07 Javascript
前端把html表格生成为excel表格的实例
2017/09/19 Javascript
详解在vue-cli中引用jQuery、bootstrap以及使用sass、less编写css
2017/11/08 jQuery
基于rollup的组件库打包体积优化小结
2018/06/18 Javascript
Vue 报错TypeError: this.$set is not a function 的解决方法
2018/12/17 Javascript
Vue程序化的事件监听器(实例方案详解)
2020/01/07 Javascript
[46:55]LGD vs Liquid 2019国际邀请赛小组赛 BO2 第一场 8.16
2019/08/19 DOTA
python 实现文件的递归拷贝实现代码
2012/08/02 Python
Python基础中所出现的异常报错总结
2016/11/19 Python
Python简单生成随机姓名的方法示例
2017/12/27 Python
加拿大花店:1800Flowers.ca
2016/11/16 全球购物
伦敦鲜花递送:Flower Station
2021/02/03 全球购物
荷兰睡眠专家:Beter Bed
2020/11/23 全球购物
两道JAVA笔试题
2016/09/14 面试题
优秀毕业大学生推荐信
2013/11/13 职场文书
上课迟到检讨书100字
2014/01/11 职场文书
给病人的慰问信
2015/03/23 职场文书
个人工作总结(管理人员)范文
2019/08/13 职场文书
python基础之文件操作
2021/10/24 Python