欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Javascript实践中的命令模式

程序员文章站 2022-07-08 22:48:47
定义encapsulate a request as an object, thereby letting you parameterize other objects with different...

定义

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命令模式的资料请关注其它相关文章!

相关标签: JS 命令模式