设计模式6—命令模式【行为型】
JS 命令模式核心是把 “请求 / 动作” 封装成独立对象,让触发者(Invoker)与执行者(Receiver)完全解耦,天然支持撤销 / 重做、命令队列、宏命令。下面从原理、角色、代码示例、应用场景与优缺点逐一说明。
一、核心定义与思想
- 定义:将一个请求封装为对象,从而可用不同请求对客户参数化、排队、记录日志,并支持可撤销操作。
- 核心思想:
- 解耦:触发者只发命令,不关心执行细节。
- 可记录 / 可撤销:命令是对象,可存历史、做 undo/redo。
- 可组合:多个命令可打包成 “宏命令” 批量执行。
二、模式角色(5 个)
| 角色 | 作用 |
|---|---|
| Command(抽象命令) | 接口,定义 execute() / undo() |
| ConcreteCommand(具体命令) | 实现接口,持有 Receiver,调用其方法 |
| Receiver(接收者) | 实际执行业务逻辑的对象 |
| Invoker(调用者) | 触发命令,不直接操作 Receiver |
| Client(客户端) | 创建 Receiver、Command,绑定关系 |
三、基础实现(灯控示例)
1. Receiver(接收者)
// 接收者:灯 class Light { turnOn() { console.log("灯已打开"); } turnOff() { console.log("灯已关闭"); } }
2. Command(抽象命令)
// 抽象命令 class Command { execute() {} undo() {} }
3. ConcreteCommand(具体命令)
// 开灯命令 class LightOnCommand extends Command { constructor(light) { super(); this.light = light; // 绑定接收者 } execute() { this.light.turnOn(); } undo() { this.light.turnOff(); } } // 关灯命令 class LightOffCommand extends Command { constructor(light) { super(); this.light = light; } execute() { this.light.turnOff(); } undo() { this.light.turnOn(); } }
4. Invoker(调用者:遥控器)
// 调用者:遥控器 class Remote { setCommand(cmd) { this.cmd = cmd; } press() { this.cmd.execute(); } pressUndo() { this.cmd.undo(); } }
5. Client(客户端使用)
// 客户端 const light = new Light(); const onCmd = new LightOnCommand(light); const offCmd = new LightOffCommand(light); const remote = new Remote(); remote.setCommand(onCmd); remote.press(); // 灯已打开 remote.setCommand(offCmd); remote.press(); // 灯已关闭 remote.pressUndo(); // 灯已打开(撤销关灯)
四、进阶:支持历史记录与撤销 / 重做
// 接收者:文本编辑器 class Editor { constructor() { this.content = ""; } add(text) { this.content += text; } del() { this.content = this.content.slice(0, -1); } } // 具体命令:添加文本 class AddCommand extends Command { constructor(editor, text) { super(); this.editor = editor; this.text = text; this.prevLen = 0; } execute() { this.prevLen = this.editor.content.length; this.editor.add(this.text); } undo() { this.editor.content = this.editor.content.slice(0, this.prevLen); } } // 调用者:命令管理器(支持历史) class CommandManager { constructor() { this.history = []; // 执行历史 this.redoStack = []; // 重做栈 } execute(cmd) { cmd.execute(); this.history.push(cmd); this.redoStack = []; // 新命令清空重做 } undo() { if (!this.history.length) return; const cmd = this.history.pop(); cmd.undo(); this.redoStack.push(cmd); } redo() { if (!this.redoStack.length) return; const cmd = this.redoStack.pop(); cmd.execute(); this.history.push(cmd); } } // 使用 const editor = new Editor(); const manager = new CommandManager(); manager.execute(new AddCommand(editor, "Hello")); console.log(editor.content); // Hello manager.execute(new AddCommand(editor, " World")); console.log(editor.content); // Hello World manager.undo(); console.log(editor.content); // Hello manager.redo(); console.log(editor.content); // Hello World
五、应用场景
- 编辑器:撤销 / 重做、历史记录(VS Code、Word)。
- GUI 按钮 / 菜单:按钮与动作解耦,同一动作绑定多入口。
- 任务队列:批量执行、延迟执行、事务回滚。
- 宏命令:组合多个命令为一个(如 “一键格式化”)。
- 状态同步:命令日志用于多端同步、操作回放。
六、优缺点
优点
- ✅ 解耦:触发者与执行者分离,互不依赖。
- ✅ 可撤销 / 重做:天然支持,只需实现
undo()。 - ✅ 易扩展:新增命令不修改旧代码,符合开闭原则。
- ✅ 可组合:支持宏命令、批量操作。
缺点
- ❌ 类膨胀:每个动作需一个命令类,增加代码量。
- ❌ 复杂度上升:多一层抽象,简单场景可能过度设计。
七、总结
命令模式的本质是 **“动作对象化”,把 “做什么” 和 “怎么做” 拆开,让代码更灵活、可追溯、可回滚。在前端,凡是需要撤销 / 重做、历史记录、批量操作 ** 的场景,都可以优先考虑命令模式。

浙公网安备 33010602011771号