命令模式(Command Pattern)
概念
命令模式是一种行为设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式将发出请求的对象(调用者)与执行请求的对象(接收者)解耦,通过引入一个命令对象来封装请求的所有信息,包括接收者、操作和参数。
基本实现
在命令模式中,通常包含以下几个核心角色:
- Command(命令接口):声明执行操作的接口。
- ConcreteCommand(具体命令):将一个接收者对象和一个或多个操作绑定在一起。
- Invoker(调用者):要求命令执行请求。
- Receiver(接收者):知道如何实施与执行一个请求相关的操作。
下面是一个简单的命令模式实现:
javascript
// 接收者 - 执行实际操作的对象
class Light {
turnOn() {
console.log('Light is ON');
}
turnOff() {
console.log('Light is OFF');
}
}
// 命令接口
class Command {
execute() {
throw new Error('execute method must be implemented');
}
undo() {
throw new Error('undo method must be implemented');
}
}
// 具体命令 - 打开灯
class TurnOnCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOn();
}
undo() {
this.light.turnOff();
}
}
// 具体命令 - 关闭灯
class TurnOffCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOff();
}
undo() {
this.light.turnOn();
}
}
// 调用者 - 遥控器
class RemoteControl {
constructor() {
this.commands = [];
this.undoCommands = [];
}
setCommand(slot, command) {
this.commands[slot] = command;
}
pressButton(slot) {
if (this.commands[slot]) {
this.commands[slot].execute();
this.undoCommands.push(this.commands[slot]);
}
}
pressUndo() {
const lastCommand = this.undoCommands.pop();
if (lastCommand) {
lastCommand.undo();
}
}
}
// 使用示例
const light = new Light();
const turnOnCommand = new TurnOnCommand(light);
const turnOffCommand = new TurnOffCommand(light);
const remote = new RemoteControl();
remote.setCommand(0, turnOnCommand);
remote.setCommand(1, turnOffCommand);
remote.pressButton(0); // 输出: Light is ON
remote.pressButton(1); // 输出: Light is OFF
remote.pressUndo(); // 输出: Light is ON
remote.pressUndo(); // 输出: Light is OFF实际应用场景
场景1:文本编辑器的撤销/重做功能
在文本编辑器中,每一步操作都可以看作是一个命令,这样可以轻松实现撤销和重做功能。
javascript
// 文本编辑器接收者
class TextEditor {
constructor() {
this.content = '';
}
insertText(text) {
this.content += text;
console.log(`Inserted text: ${text}`);
console.log(`Current content: ${this.content}`);
}
deleteText(length) {
const deleted = this.content.slice(-length);
this.content = this.content.slice(0, -length);
console.log(`Deleted text: ${deleted}`);
console.log(`Current content: ${this.content}`);
}
getContent() {
return this.content;
}
}
// 抽象命令类
class TextCommand {
execute() {}
undo() {}
}
// 插入文本命令
class InsertTextCommand extends TextCommand {
constructor(editor, text) {
super();
this.editor = editor;
this.text = text;
this.previousContent = '';
}
execute() {
this.previousContent = this.editor.getContent();
this.editor.insertText(this.text);
}
undo() {
this.editor.content = this.previousContent;
console.log(`Undo insert: ${this.text}`);
console.log(`Restored content: ${this.editor.getContent()}`);
}
}
// 删除文本命令
class DeleteTextCommand extends TextCommand {
constructor(editor, length) {
super();
this.editor = editor;
this.length = length;
this.deletedText = '';
this.previousContent = '';
}
execute() {
this.previousContent = this.editor.getContent();
this.deletedText = this.editor.getContent().slice(-this.length);
this.editor.deleteText(this.length);
}
undo() {
this.editor.content = this.previousContent;
console.log(`Undo delete: ${this.deletedText}`);
console.log(`Restored content: ${this.editor.getContent()}`);
}
}
// 命令管理器
class CommandManager {
constructor() {
this.history = [];
this.undoStack = [];
}
executeCommand(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 editor = new TextEditor();
const commandManager = new CommandManager();
const insertCmd1 = new InsertTextCommand(editor, 'Hello ');
const insertCmd2 = new InsertTextCommand(editor, 'World!');
const deleteCmd = new DeleteTextCommand(editor, 6);
commandManager.executeCommand(insertCmd1); // 插入 "Hello "
commandManager.executeCommand(insertCmd2); // 插入 "World!"
commandManager.executeCommand(deleteCmd); // 删除最后6个字符
console.log('--- Undo Operations ---');
commandManager.undo(); // 撤销删除
commandManager.undo(); // 撤销插入 "World!"
console.log('--- Redo Operations ---');
commandManager.redo(); // 重做插入 "World!"场景2:餐厅点餐系统
在餐厅点餐系统中,服务员(调用者)接受顾客的订单(命令),然后传给厨房(接收者)处理。
javascript
// 厨房接收者
class Kitchen {
prepareBurger() {
console.log('Preparing burger...');
return 'Burger';
}
prepareFries() {
console.log('Preparing fries...');
return 'Fries';
}
prepareDrink() {
console.log('Preparing drink...');
return 'Drink';
}
}
// 订单命令接口
class OrderCommand {
execute() {}
getItemName() {}
}
// 汉堡订单命令
class BurgerOrder extends OrderCommand {
constructor(kitchen) {
super();
this.kitchen = kitchen;
}
execute() {
return this.kitchen.prepareBurger();
}
getItemName() {
return 'Burger';
}
}
// 薯条订单命令
class FriesOrder extends OrderCommand {
constructor(kitchen) {
super();
this.kitchen = kitchen;
}
execute() {
return this.kitchen.prepareFries();
}
getItemName() {
return 'Fries';
}
}
// 饮料订单命令
class DrinkOrder extends OrderCommand {
constructor(kitchen) {
super();
this.kitchen = kitchen;
}
execute() {
return this.kitchen.prepareDrink();
}
getItemName() {
return 'Drink';
}
}
// 服务员 - 调用者
class Waiter {
constructor() {
this.orders = [];
this.completedOrders = [];
}
takeOrder(order) {
this.orders.push(order);
console.log(`Order taken: ${order.getItemName()}`);
}
sendOrdersToKitchen() {
console.log('\nSending orders to kitchen:');
this.orders.forEach((order, index) => {
const item = order.execute();
this.completedOrders.push(item);
console.log(`${index + 1}. ${item} prepared`);
});
this.orders = [];
}
serveCompletedOrders() {
console.log('\nServing completed orders:');
this.completedOrders.forEach((item, index) => {
console.log(`${index + 1}. Serving ${item}`);
});
this.completedOrders = [];
}
}
// 使用示例
const kitchen = new Kitchen();
const waiter = new Waiter();
// 顾客下单
waiter.takeOrder(new BurgerOrder(kitchen));
waiter.takeOrder(new FriesOrder(kitchen));
waiter.takeOrder(new DrinkOrder(kitchen));
// 发送到厨房制作
waiter.sendOrdersToKitchen();
// 上菜
waiter.serveCompletedOrders();场景3:游戏中的输入控制
在游戏中,玩家的各种操作可以通过命令模式来处理,方便添加新操作和实现回放功能。
javascript
// 游戏角色接收者
class GameCharacter {
constructor(name) {
this.name = name;
this.position = { x: 0, y: 0 };
this.health = 100;
}
move(direction) {
switch (direction) {
case 'up':
this.position.y -= 1;
break;
case 'down':
this.position.y += 1;
break;
case 'left':
this.position.x -= 1;
break;
case 'right':
this.position.x += 1;
break;
}
console.log(`${this.name} moved ${direction} to position (${this.position.x}, ${this.position.y})`);
}
attack() {
console.log(`${this.name} attacks!`);
}
defend() {
console.log(`${this.name} defends!`);
}
jump() {
console.log(`${this.name} jumps!`);
}
getStatus() {
return {
name: this.name,
position: { ...this.position },
health: this.health
};
}
}
// 游戏命令接口
class GameCommand {
execute() {}
undo() {}
}
// 移动命令
class MoveCommand extends GameCommand {
constructor(character, direction) {
super();
this.character = character;
this.direction = direction;
this.previousPosition = { ...character.position };
}
execute() {
this.character.move(this.direction);
}
undo() {
this.character.position = this.previousPosition;
console.log(`${this.character.name} position restored to (${this.character.position.x}, ${this.character.position.y})`);
}
}
// 攻击命令
class AttackCommand extends GameCommand {
constructor(character) {
super();
this.character = character;
}
execute() {
this.character.attack();
}
undo() {
console.log(`${this.character.name}'s attack cancelled`);
}
}
// 防御命令
class DefendCommand extends GameCommand {
constructor(character) {
super();
this.character = character;
}
execute() {
this.character.defend();
}
undo() {
console.log(`${this.character.name}'s defense cancelled`);
}
}
// 跳跃命令
class JumpCommand extends GameCommand {
constructor(character) {
super();
this.character = character;
}
execute() {
this.character.jump();
}
undo() {
console.log(`${this.character.name}'s jump cancelled`);
}
}
// 输入处理器 - 调用者
class InputHandler {
constructor() {
this.commandHistory = [];
}
handleInput(command) {
command.execute();
this.commandHistory.push(command);
}
replay() {
console.log('\n--- Replaying actions ---');
this.commandHistory.forEach(command => {
command.execute();
});
}
undoLastAction() {
const lastCommand = this.commandHistory.pop();
if (lastCommand) {
lastCommand.undo();
}
}
}
// 使用示例
const player = new GameCharacter('Hero');
const inputHandler = new InputHandler();
// 玩家操作序列
inputHandler.handleInput(new MoveCommand(player, 'right'));
inputHandler.handleInput(new MoveCommand(player, 'up'));
inputHandler.handleInput(new AttackCommand(player));
inputHandler.handleInput(new JumpCommand(player));
inputHandler.handleInput(new DefendCommand(player));
console.log('\nPlayer Status:', player.getStatus());
// 撤销最后一个动作
console.log('\n--- Undo Last Action ---');
inputHandler.undoLastAction();
console.log('\nPlayer Status:', player.getStatus());
// 回放所有动作
inputHandler.replay();关键要点
解耦调用者和接收者:命令模式将发出请求的对象与执行请求的对象解耦,使得系统更加灵活。
支持撤销操作:通过在命令对象中实现 undo 方法,可以轻松实现撤销功能。
易于扩展新的命令:增加新的命令只需要创建新的命令类,符合开闭原则。
支持宏命令:可以将多个命令组合成一个复合命令,批量执行。
队列请求和日志记录:命令可以被存储在队列中,或者记录到日志中以便后续处理。
与其他模式的关系
- 与备忘录模式结合:命令模式的撤销操作常常与备忘录模式配合使用,保存对象的状态快照。
- 与组合模式结合:可以用组合模式来实现宏命令,将多个简单命令组合成复杂命令。
- 与策略模式区别:虽然两者都封装了算法,但命令模式关注的是封装请求,而策略模式关注的是算法族的替换。
优缺点
优点:
- 降低系统的耦合度:将调用操作的对象与知道如何实现该操作的对象解耦。
- 容易设计一个命令队列和宏命令:因为请求是封装在命令对象中的,所以可以把这些命令放入队列中。
- 容易实现撤销和恢复功能:只需为命令增加 undo 和 redo 方法即可。
- 新增命令很容易:无需修改现有类,符合开闭原则。
缺点:
- 可能导致系统中有过多的具体命令类:因为每一个命令都需要设计一个具体命令类。
- 增加了系统的复杂性:引入了额外的类和对象,可能使系统变得复杂。
命令模式广泛应用于需要撤销操作、事务处理、GUI菜单项、线程池任务等场景,是实现松耦合架构的重要手段之一。