詳解Javascript實踐中的命令模式
定義
Encapsulate a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests,and support undoable operations.“
「命令模式」將「請求」封裝成對象,以便使用不同的請求、隊列或者日志來參數(shù)化其他對象,同時支持可撤消的操作。
這里的「請求」的定義,并不是我們前端常說的「Ajax 請求」,而是一個「動作請求」,也就是發(fā)起一個行為。例如,通過遙控器關(guān)閉電視,這里的「關(guān)閉」就是一個請求。在命令模式中,我們將請求抽象成一個命令,這個命令是可復(fù)用的,它只關(guān)心它的接受者(電視);而對于動作的發(fā)起者(遙控器)來說,它只關(guān)心它所支持的命令有哪些,而不關(guān)心這些命令具體是做什么的。
結(jié)構(gòu)
命令模式的類圖如下:

在該類圖中,我們看到五個角色:
- Client - 創(chuàng)建 Concrete Command 與 Receiver(應(yīng)用層)。
- Invoker - 命令的發(fā)出者,通常會持有命令對象,可以持有很多的命令對象。
- Receiver - 命令接收者,真正執(zhí)行命令的對象。任何類都可能成為一個接收者,只要它能夠?qū)崿F(xiàn)命令要求實現(xiàn)的相應(yīng)功能。
- Command - 命令接口。
- ConcreteCommand - 命令接口的實現(xiàn)。
Reciver 與 Invoker 沒有耦合,當(dāng)需要拓展功能時,通過新增 Command,因此命令模式符合開閉原則。
實例
自定義快捷鍵
自定義快捷鍵是一個編輯器的最基本功能。通過命令模式,我們可以寫出一個將鍵位與鍵位邏輯解耦的結(jié)構(gòu)。
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。當(dāng)我們需要修改已有的 keymap 時,只需要新增或替換已有的key或Command即可。
是不是覺得這個寫法似曾相識?沒錯Redux 也是應(yīng)用了命令模式,Store 相當(dāng)于 Receiver,Action 相當(dāng)于 Command,Dispatch 相當(dāng)于 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)
當(dāng)我們有了命令隊列,我們又能夠很容易得進行多次的撤銷和重做,實現(xiàn)一個命令的歷史記錄。只需要移動當(dāng)前命令隊列的指針即可。
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--
}
}
同時,如果我們將命令序列化成一個對象,它便可以用于保存與傳遞。這樣我們將它發(fā)送到遠程計算機,就能實現(xiàn)遠程控制ezio移動的功能。
[{
type: 'move',
x: 1,
y: 1,
}, {
type: 'move',
x: 2,
y: 2,
}]
宏命令
對Command進行一些簡單的處理就能夠?qū)⒁延械拿罱M合起來執(zhí)行,將其變成一個宏命令。
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()
總結(jié)
通過以上幾個例子,我們可以看出命令模式有一下幾個特點:
- 低耦合,徹底消除了接受者與調(diào)用者之間的耦合。
- 易拓展,只需要增加新的命令便可拓展出新功能。
- 支持序列化,易于實現(xiàn)保存與傳遞。
- 容易導(dǎo)致 Command 類龐大。
以上就是詳解Javascript實踐中的命令模式的詳細內(nèi)容,更多關(guān)于Javascript命令模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
bootstrap響應(yīng)式導(dǎo)航條模板使用詳解(含下拉菜單,彈出框)
這篇文章主要為大家詳細介紹了bootstrap響應(yīng)式導(dǎo)航條模板使用詳解,含下拉菜單,彈出框效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
JavaScript高級程序設(shè)計 讀書筆記之十一 內(nèi)置對象Global
由ECMAScript實現(xiàn)提供的、獨立于宿主環(huán)境的所有對象,在ECMAScript程序開始執(zhí)行時出現(xiàn)2012-03-03

