什么是命令模式?JavaScript 实现一个命令模式的示例
·
什么是命令模式?
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,使你可以用不同的请求参数化其他对象(如队列、日志),并支持请求的撤销、排队和延迟执行。
其核心思想是:将 “发出命令的对象(调用者)” 与 “执行命令的对象(接收者)” 解耦,通过一个 “命令对象” 作为中间层,封装具体的操作和参数。调用者只需触发命令,无需知道命令如何执行;接收者只需执行命令,无需知道谁发起了命令。
命令模式的核心角色
- 命令接口(Command):定义执行命令的统一方法(如
execute())和撤销命令的方法(如undo())。 - 具体命令(ConcreteCommand):实现命令接口,持有接收者的引用,在
execute()中调用接收者的具体操作,在undo()中实现反向操作。 - 接收者(Receiver):执行命令的实际对象,包含具体的业务逻辑(如 “开灯”“关灯”)。
- 调用者(Invoker):接收命令并触发执行(如按钮、遥控器),可以存储命令队列,支持批量执行或撤销。
JavaScript 实现命令模式示例:智能遥控器
以下以 “智能遥控器” 为例,实现命令模式。场景是:遥控器有多个按钮,每个按钮对应一个命令(如开灯、开空调、开电视),支持执行命令和撤销上一次命令。
代码实现
// -------------- 1. 接收者(Receiver):各种设备 --------------
// 灯光设备
class Light {
turnOn() {
console.log("灯光已打开");
}
turnOff() {
console.log("灯光已关闭");
}
}
// 空调设备
class AirConditioner {
turnOn() {
console.log("空调已打开(26℃)");
}
turnOff() {
console.log("空调已关闭");
}
}
// 电视设备
class TV {
turnOn() {
console.log("电视已打开");
}
turnOff() {
console.log("电视已关闭");
}
}
// -------------- 2. 命令接口(Command)--------------
class Command {
execute() {
throw new Error("子类必须实现 execute 方法");
}
undo() {
throw new Error("子类必须实现 undo 方法");
}
}
// -------------- 3. 具体命令(ConcreteCommand)--------------
// 开灯命令
class LightOnCommand extends Command {
constructor(light) {
super();
this.light = light; // 持有接收者(灯光)的引用
}
// 执行:调用灯光的 turnOn
execute() {
this.light.turnOn();
}
// 撤销:调用灯光的 turnOff(反向操作)
undo() {
this.light.turnOff();
}
}
// 关灯命令
class LightOffCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOff();
}
undo() {
this.light.turnOn();
}
}
// 开空调命令
class AirConditionerOnCommand extends Command {
constructor(ac) {
super();
this.ac = ac;
}
execute() {
this.ac.turnOn();
}
undo() {
this.ac.turnOff();
}
}
// 开电视命令
class TVOnCommand extends Command {
constructor(tv) {
super();
this.tv = tv;
}
execute() {
this.tv.turnOn();
}
undo() {
this.tv.turnOff();
}
}
// -------------- 4. 调用者(Invoker):遥控器 --------------
class RemoteControl {
constructor() {
this.command = null; // 当前命令
this.lastCommand = null; // 记录上一次执行的命令(用于撤销)
}
// 设置当前命令(绑定按钮与命令)
setCommand(command) {
this.command = command;
}
// 按下按钮:执行命令并记录
pressButton() {
if (this.command) {
this.command.execute();
this.lastCommand = this.command; // 保存最后执行的命令
}
}
// 按下撤销按钮:撤销上一次命令
pressUndoButton() {
if (this.lastCommand) {
console.log("撤销操作:");
this.lastCommand.undo();
this.lastCommand = null; // 撤销后清空记录(可根据需求调整)
}
}
}
// -------------- 5. 客户端使用 --------------
// 创建接收者(设备)
const light = new Light();
const ac = new AirConditioner();
const tv = new TV();
// 创建具体命令(绑定设备)
const lightOn = new LightOnCommand(light);
const acOn = new AirConditionerOnCommand(ac);
const tvOn = new TVOnCommand(tv);
const lightOff = new LightOffCommand(light);
// 创建调用者(遥控器)
const remote = new RemoteControl();
// 场景1:按按钮开灯
remote.setCommand(lightOn);
remote.pressButton(); // 输出:灯光已打开
// 场景2:按按钮开空调
remote.setCommand(acOn);
remote.pressButton(); // 输出:空调已打开(26℃)
// 场景3:撤销上一次操作(关空调)
remote.pressUndoButton(); // 输出:撤销操作:空调已关闭
// 场景4:按按钮开电视
remote.setCommand(tvOn);
remote.pressButton(); // 输出:电视已打开
// 场景5:关灯
remote.setCommand(lightOff);
remote.pressButton(); // 输出:灯光已关闭
// 场景6:撤销上一次操作(开灯)
remote.pressUndoButton(); // 输出:撤销操作:灯光已打开
代码说明
-
接收者(Light、AirConditioner 等):实现具体的业务逻辑(如
turnOn、turnOff),是命令的实际执行者,但不知道谁会调用它。 -
命令接口(Command):定义
execute()(执行命令)和undo()(撤销命令)方法,确保所有命令遵循统一接口。 -
具体命令(如 LightOnCommand):
- 持有接收者的引用(如
light),在execute()中调用接收者的操作(如light.turnOn())。 - 在
undo()中实现反向操作(如light.turnOff()),使命令可撤销。
- 持有接收者的引用(如
-
调用者(RemoteControl):
- 通过
setCommand()绑定命令(如给按钮分配 “开灯” 命令)。 pressButton()触发命令执行,并记录最后一次命令(用于撤销)。pressUndoButton()调用上一次命令的undo()方法,实现撤销功能。
- 通过
-
核心优势:
- 解耦调用者与接收者:遥控器(调用者)无需知道设备(接收者)的细节,只需触发命令;设备也无需知道谁在调用它。
- 支持撤销与排队:通过记录命令历史,可实现撤销、重做;也可将命令放入队列,实现批量执行或延迟执行(如定时任务)。
- 易于扩展:新增命令(如 “调节空调温度”)只需添加新的具体命令类,无需修改调用者或接收者,符合 “开闭原则”。
命令模式的常见应用场景
- 撤销 / 重做功能:如文本编辑器的撤销操作(每个编辑动作封装为命令,记录历史以便撤销)。
- 请求排队:如任务调度系统(将任务封装为命令,放入队列按顺序执行)。
- 日志与事务:如数据库事务(将一系列操作封装为命令,成功则提交,失败则通过
undo回滚)。 - GUI 交互:如按钮点击、菜单选择(每个 UI 元素绑定一个命令,触发时执行)。
命令模式的核心是 “封装请求为对象,解耦调用与执行”,它通过命令对象将操作参数化,使系统更灵活、可扩展,尤其适合需要支持撤销、排队的场景。
更多推荐


所有评论(0)