命令模式
将请求封装成对象,实现撤销、重做和命令队列
问题
什么是命令模式?如何在前端中应用命令模式实现撤销/重做功能?
解答
命令模式将”请求”封装成对象,就像江湖中的通缉令——发布者不需要知道谁来执行,执行者也不需要知道谁发布的,通缉令本身就包含了所有必要信息。
基本结构
// 命令接口
class Command {
execute() {}
undo() {}
}
// 具体命令:加法
class AddCommand extends Command {
constructor(receiver, value) {
super()
this.receiver = receiver
this.value = value
}
execute() {
this.receiver.add(this.value)
}
undo() {
this.receiver.subtract(this.value)
}
}
// 接收者:计算器
class Calculator {
constructor() {
this.result = 0
}
add(value) {
this.result += value
console.log(`结果: ${this.result}`)
}
subtract(value) {
this.result -= value
console.log(`结果: ${this.result}`)
}
}
// 调用者:支持撤销/重做
class CommandManager {
constructor() {
this.history = [] // 已执行的命令
this.undoStack = [] // 已撤销的命令
}
execute(command) {
command.execute()
this.history.push(command)
this.undoStack = [] // 执行新命令后清空重做栈
}
undo() {
const command = this.history.pop()
if (command) {
command.undo()
this.undoStack.push(command)
}
}
redo() {
const command = this.undoStack.pop()
if (command) {
command.execute()
this.history.push(command)
}
}
}
使用示例
const calculator = new Calculator()
const manager = new CommandManager()
// 执行命令
manager.execute(new AddCommand(calculator, 10)) // 结果: 10
manager.execute(new AddCommand(calculator, 20)) // 结果: 30
// 撤销
manager.undo() // 结果: 10
manager.undo() // 结果: 0
// 重做
manager.redo() // 结果: 10
实际应用:文本编辑器
// 文本编辑器接收者
class TextEditor {
constructor() {
this.content = ''
}
insert(text, position) {
this.content =
this.content.slice(0, position) +
text +
this.content.slice(position)
}
delete(position, length) {
this.content =
this.content.slice(0, position) +
this.content.slice(position + length)
}
getContent() {
return this.content
}
}
// 插入命令
class InsertCommand extends Command {
constructor(editor, text, position) {
super()
this.editor = editor
this.text = text
this.position = position
}
execute() {
this.editor.insert(this.text, this.position)
}
undo() {
this.editor.delete(this.position, this.text.length)
}
}
// 删除命令
class DeleteCommand extends Command {
constructor(editor, position, length) {
super()
this.editor = editor
this.position = position
this.length = length
this.deletedText = '' // 保存被删除的文本用于撤销
}
execute() {
// 保存将被删除的文本
this.deletedText = this.editor.content.slice(
this.position,
this.position + this.length
)
this.editor.delete(this.position, this.length)
}
undo() {
this.editor.insert(this.deletedText, this.position)
}
}
// 使用
const editor = new TextEditor()
const cmdManager = new CommandManager()
cmdManager.execute(new InsertCommand(editor, 'Hello', 0))
console.log(editor.getContent()) // "Hello"
cmdManager.execute(new InsertCommand(editor, ' World', 5))
console.log(editor.getContent()) // "Hello World"
cmdManager.undo()
console.log(editor.getContent()) // "Hello"
cmdManager.redo()
console.log(editor.getContent()) // "Hello World"
宏命令:批量执行
// 宏命令:组合多个命令
class MacroCommand extends Command {
constructor() {
super()
this.commands = []
}
add(command) {
this.commands.push(command)
}
execute() {
this.commands.forEach(cmd => cmd.execute())
}
undo() {
// 逆序撤销
this.commands.slice().reverse().forEach(cmd => cmd.undo())
}
}
// 使用宏命令
const macro = new MacroCommand()
macro.add(new AddCommand(calculator, 5))
macro.add(new AddCommand(calculator, 10))
macro.add(new AddCommand(calculator, 15))
manager.execute(macro) // 一次执行三个命令
manager.undo() // 一次撤销三个命令
关键点
- 解耦:发送者和接收者互不依赖,通过命令对象通信
- 可撤销:命令对象保存执行状态,实现 undo/redo
- 可组合:宏命令可以将多个命令组合成一个
- 可记录:命令队列可用于日志、事务、延迟执行
- 前端场景:富文本编辑器、画板工具、表单操作、游戏回放
目录