Javascript 实践中的命令模式

更新日期: 2019-12-27阅读: 1.9k标签: 命令

定义

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

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

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


结构

命令模式的类图如下:


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

  • 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 类庞大。

链接: https://www.fly63.com/article/detial/7805

kubernetes 命令方式 部署、访问应用

Kubernetes 命令的方式运行应用:默认情况下,所有Pod只能在集群内部访问,为了能够外部访问应用,需要将容器端口映射到节点的端口,15672 就是容器中端口。15672端口已经映射到了12732端口,端口号是随机分配的

node.js中使用yargs来处理命令行参数

yargs库能够方便的处理命令行参数。读取命令行参数argv 对象用来保存命令行参数,传递参数时,参数名以 -- 开头,中间使用 = 或 空格,然后接上值 。argv 有一个 下划线 属性,该属性用来获取非连词线开头的参数

Linux常用命令总汇

ls命令就是list的缩写,通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限(包括目录、文件夹、文件权限)查看目录信息等等

常用的搜索命令_高级搜索指令大全

作为一名资深SEO,不懂得搜索引擎高级指令的使用显然是说不过去的。搜索引擎高级指令就如同语言一样,用来建立搜索引擎与SEO人员直接沟通的“通道”,下面就为大家整理下常用的高级搜索命令

npm常用命令与操作

npm常用命令与操作:npm i/install moduleName安装模块;i是install的缩写,两者功能是一样的;npm ls 查看所有局部安装的模块; npm从5.0版本开始添加了package-lock.json文件(下称lock文件);用于保证再次安装模块时能安装同样的版本;

Linux常用命令

玩过Linux的人都会知道,Linux中的命令的确是非常多,但是玩过Linux的人也从来不会因为Linux的命令如此之多而烦恼,因为我们只需要掌握我们最常用的命令

每个Web开发者都该了解的12条命令行

在开发者的弹药箱里,命令行是最具生产力的工具之一。掌握它们可以给你的工作流程带来非常积极的影响。因为,许多日常任务都可以用一条命令然后按回车来解决。

FFmpeg常用命令合集

视频裁剪滤镜(播放器大小裁剪): ffmpeg -i killer.mp4 -vf crop=in_w-200:in_h-200 -c:v libx264 -c:a copy out.mp4;视频裁剪(按时间裁剪): ffmpeg -i shanguangshaonv.mp4 -ss

NodeJS 构建现代化的命令行工具

这是一篇关于如何使用 NodeJS 构建高性能、高可读性的现代化命令行工具的博客。每当我们想要创建一个基于 NodeJS 的命令行工具时,就会衍生出一堆问题需要解决,比如如何准备开发环境,如何打包转译代码

python常用命令

#查看django版本;创建一个项目mysite;在当前目录下启动manage.py;创建一个polls应用;在当前目录下针对models生成sql语句;根据生成的sql语句生成数据库

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!