JavaScript 设计模式 - 命令模式详解
·
一、概述
命令模式(Command Pattern)是一种行为设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化。命令模式的核心思想是将方法调用、请求或操作封装到一个单独的对象中,并给这个对象提供执行操作和撤销操作的方法。
在命令模式中,发送者(Invoker)与接收者(Receiver)之间完全解耦,发送者只需要知道如何发送命令,而不需要知道命令是如何被接收者执行的。
二、作用
命令模式的主要作用包括:
- 解耦发送者和接收者:发送者不需要知道接收者的具体实现
- 支持撤销操作:可以轻松实现命令的撤销和重做功能
- 支持命令队列:可以将命令存储在队列中,按顺序执行
- 支持宏命令:可以将多个命令组合成一个复合命令
- 易于扩展:新增命令不需要修改现有代码,符合开闭原则
三、命令模式例子
1、菜单程序
命令模式最常见的应用场景之一就是菜单系统,每个菜单项对应一个命令:
// 接收者 - 文本编辑器
const createTextEditor = () => {
let content = '';
return {
copy: () => {
console.log('复制文本');
return content;
},
paste: (text) => {
content += text;
console.log(`粘贴文本: ${text}`);
},
cut: () => {
const text = content;
content = '';
console.log('剪切文本');
return text;
},
getContent: () => content
};
};
// 创建命令工厂函数
const createCopyCommand = (editor) => {
let copiedText = '';
return {
execute: () => {
copiedText = editor.copy();
console.log('执行复制命令');
},
undo: () => {
console.log('撤销复制命令');
}
};
};
const createPasteCommand = (editor, text) => {
return {
execute: () => {
editor.paste(text);
console.log('执行粘贴命令');
},
undo: () => {
console.log('撤销粘贴命令');
}
};
};
// 调用者 - 菜单
const createMenu = () => {
const commands = [];
return {
addCommand: (command) => {
commands.push(command);
},
click: (index) => {
if (commands[index]) {
commands[index].execute();
}
}
};
};
// 使用示例
const editor = createTextEditor();
const menu = createMenu();
menu.addCommand(createCopyCommand(editor));
menu.addCommand(createPasteCommand(editor, 'Hello World'));
menu.click(0); // 执行复制命令
menu.click(1); // 执行粘贴命令
2、撤销命令
命令模式非常适合实现撤销功能:
const createCalculator = () => {
let value = 0;
return {
add: (num) => {
value += num;
console.log(`加上 ${num},当前值: ${value}`);
},
subtract: (num) => {
value -= num;
console.log(`减去 ${num},当前值: ${value}`);
},
getValue: () => value,
setValue: (newValue) => {
value = newValue;
}
};
};
const createAddCommand = (calculator, value) => {
let previousValue = 0;
return {
execute: () => {
previousValue = calculator.getValue();
calculator.add(value);
},
undo: () => {
calculator.setValue(previousValue);
console.log(`撤销加法操作,恢复值: ${previousValue}`);
}
};
};
const createSubtractCommand = (calculator, value) => {
let previousValue = 0;
return {
execute: () => {
previousValue = calculator.getValue();
calculator.subtract(value);
},
undo: () => {
calculator.setValue(previousValue);
console.log(`撤销减法操作,恢复值: ${previousValue}`);
}
};
};
const createCommandManager = () => {
let history = [];
let current = -1;
return {
execute: (command) => {
// 执行新命令时,删除当前点之后的历史
history = history.slice(0, current + 1);
command.execute();
history.push(command);
current = history.length - 1;
},
undo: () => {
if (current >= 0) {
const command = history[current];
command.undo();
current--;
return true;
} else {
console.log('没有可撤销的操作');
return false;
}
},
redo: () => {
if (current < history.length - 1) {
current++;
const command = history[current];
command.execute();
return true;
} else {
console.log('没有可重做的操作');
return false;
}
},
canUndo: () => current >= 0,
canRedo: () => current < history.length - 1
};
};
// 使用示例
const calculator = createCalculator();
const commandManager = createCommandManager();
commandManager.execute(createAddCommand(calculator, 10)); // 值变为10
commandManager.execute(createSubtractCommand(calculator, 3)); // 值变为7
commandManager.execute(createAddCommand(calculator, 5)); // 值变为12
commandManager.undo(); // 撤销加5,值变为7
commandManager.undo(); // 撤销减3,值变为10
commandManager.redo(); // 重做减3,值变为7
3、命令队列
命令模式支持将命令存储在队列中按顺序执行:
const createCommandQueue = () => {
const commands = [];
let isRunning = false;
const executeNext = () => {
if (commands.length > 0) {
isRunning = true;
const command = commands.shift();
command.execute(() => {
isRunning = false;
executeNext(); // 执行下一个命令
});
}
};
return {
add: (command) => {
commands.push(command);
if (!isRunning) {
executeNext();
}
}
};
};
const createAsyncCommand = (action, delay) => {
return {
execute: (callback) => {
console.log(`开始执行命令: ${action}`);
setTimeout(() => {
console.log(`完成命令: ${action}`);
callback();
}, delay);
}
};
};
// 使用示例
const queue = createCommandQueue();
queue.add(createAsyncCommand('下载文件', 1000));
queue.add(createAsyncCommand('处理数据', 2000));
queue.add(createAsyncCommand('保存结果', 1500));
4、宏命令
宏命令是将多个命令组合成一个复合命令:
const createMacroCommand = () => {
const commands = [];
return {
add: (command) => {
commands.push(command);
},
execute: () => {
commands.forEach(command => command.execute());
},
undo: () => {
// 逆序撤销
for (let i = commands.length - 1; i >= 0; i--) {
commands[i].undo();
}
}
};
};
// 使用示例
const macro = createMacroCommand();
macro.add(createCopyCommand(editor));
macro.add(createPasteCommand(editor, 'Hello'));
macro.add(createPasteCommand(editor, ' World'));
macro.execute(); // 依次执行所有命令
5、傻瓜命令
傻瓜命令是指命令对象只需要调用execute方法,不需要接收者参与:
const createSimpleCommand = (action) => {
return {
execute: () => {
action();
}
};
};
// 使用示例
const command = createSimpleCommand(() => {
console.log('执行简单操作');
});
command.execute();
6、智能命令
智能命令是指命令对象自己处理请求,不需要额外的接收者:
const createSmartCommand = (process) => {
return {
execute: () => {
console.log('智能命令自己处理请求');
process();
}
};
};
const smartCommand = createSmartCommand(() => {
console.log('处理具体业务逻辑');
});
smartCommand.execute();
四、Vue实现案例
案例一:通用命令管理器
<template>
<div class="command-demo">
<h2>通用命令管理器</h2>
<div class="actions">
<button @click="executeCommand('increment')">增加</button>
<button @click="executeCommand('decrement')">减少</button>
<button @click="executeCommand('reset')">重置</button>
<button @click="undo" :disabled="!canUndo">撤销</button>
<button @click="redo" :disabled="!canRedo">重做</button>
</div>
<div class="display">
当前值: {{ value }}
</div>
</div>
</template>
<script>
export default {
name: 'CommandManager',
data() {
return {
value: 0,
history: [],
current: -1
}
},
computed: {
canUndo() {
return this.current >= 0;
},
canRedo() {
return this.current < this.history.length - 1;
}
},
methods: {
// 创建各种命令
createIncrementCommand() {
const previousValue = this.value;
return {
execute: () => {
this.value++;
},
undo: () => {
this.value = previousValue;
}
};
},
createDecrementCommand() {
const previousValue = this.value;
return {
execute: () => {
this.value--;
},
undo: () => {
this.value = previousValue;
}
};
},
createResetCommand() {
const previousValue = this.value;
return {
execute: () => {
this.value = 0;
},
undo: () => {
this.value = previousValue;
}
};
},
// 执行命令
executeCommand(type) {
let command;
switch (type) {
case 'increment':
command = this.createIncrementCommand();
break;
case 'decrement':
command = this.createDecrementCommand();
break;
case 'reset':
command = this.createResetCommand();
break;
default:
return;
}
// 清除当前点之后的历史
this.history = this.history.slice(0, this.current + 1);
command.execute();
this.history.push(command);
this.current = this.history.length - 1;
},
// 撤销操作
undo() {
if (this.current >= 0) {
const command = this.history[this.current];
command.undo();
this.current--;
}
},
// 重做操作
redo() {
if (this.current < this.history.length - 1) {
this.current++;
const command = this.history[this.current];
command.execute();
}
}
}
}
</script>
<style scoped>
.command-demo {
max-width: 400px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.actions {
margin-bottom: 20px;
}
.actions button {
margin: 5px;
padding: 8px 16px;
border: 1px solid #999;
border-radius: 4px;
cursor: pointer;
}
.actions button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.display {
font-size: 18px;
text-align: center;
padding: 15px;
background-color: #f5f5f5;
border-radius: 4px;
}
</style>
案例二:通用操作队列
<template>
<div class="operation-queue">
<h2>操作队列示例</h2>
<div class="controls">
<button @click="addOperation('operation1')">操作1</button>
<button @click="addOperation('operation2')">操作2</button>
<button @click="addOperation('operation3')">操作3</button>
<button @click="clearQueue">清空队列</button>
</div>
<div class="status">
<p>队列状态: {{ isRunning ? '执行中' : '空闲' }}</p>
<p>队列长度: {{ queue.length }}</p>
</div>
<div class="log">
<h3>操作日志:</h3>
<ul>
<li v-for="(log, index) in logs" :key="index">{{ log }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
name: 'OperationQueue',
data() {
return {
queue: [],
isRunning: false,
logs: []
}
},
methods: {
// 创建异步操作命令
createAsyncOperation(name, duration) {
return {
execute: (callback) => {
this.logs.push(`开始执行 ${name}`);
setTimeout(() => {
this.logs.push(`完成 ${name}`);
callback();
}, duration);
}
};
},
// 添加操作到队列
addOperation(type) {
let command;
switch (type) {
case 'operation1':
command = this.createAsyncOperation('操作1', 1000);
break;
case 'operation2':
command = this.createAsyncOperation('操作2', 1500);
break;
case 'operation3':
command = this.createAsyncOperation('操作3', 800);
break;
default:
return;
}
this.queue.push(command);
this.logs.push(`添加 ${type} 到队列`);
if (!this.isRunning) {
this.executeNext();
}
},
// 执行队列中的下一个操作
executeNext() {
if (this.queue.length > 0) {
this.isRunning = true;
const command = this.queue.shift();
command.execute(() => {
this.isRunning = false;
this.executeNext(); // 执行下一个命令
});
}
},
// 清空队列
clearQueue() {
this.queue = [];
this.logs.push('队列已清空');
}
}
}
</script>
<style scoped>
.operation-queue {
max-width: 500px;
margin: 20px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.controls button {
margin: 5px;
padding: 10px 15px;
border: 1px solid #999;
border-radius: 4px;
cursor: pointer;
}
.status {
margin: 15px 0;
padding: 10px;
background-color: #f0f0f0;
border-radius: 4px;
}
.status p {
margin: 5px 0;
}
.log {
margin-top: 20px;
}
.log ul {
max-height: 200px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
border-radius: 4px;
}
.log li {
margin: 5px 0;
padding: 3px 0;
border-bottom: 1px solid #eee;
}
</style>
五、总结
优点
- 降低耦合度:发送者和接收者完全解耦,符合迪米特法则
- 易于扩展:新增命令类非常容易,符合开闭原则
- 支持撤销操作:可以轻松实现命令的撤销和重做功能
- 支持命令队列:可以将命令存储在队列中,实现批量执行
- 支持宏命令:可以将多个命令组合成一个复合命令
- 易于记录日志:可以方便地记录命令执行历史
缺点
- 增加系统复杂性:每个命令都需要创建一个具体类,可能会导致类数量增加
- 内存开销:为了实现撤销功能,需要存储命令执行前的状态
- 代码量增加:相比直接调用方法,命令模式需要更多的代码
适用场景
命令模式适用于以下场景:
- 需要参数化对象以执行不同的请求
- 需要将请求排队或记录请求日志
- 需要支持撤销操作
- 系统需要在不同时间指定、排列和执行请求
- 系统需要将操作组合成宏命令
通过合理运用命令模式,可以构建更加灵活、可维护的应用程序,特别是在需要支持撤销操作和复杂命令处理的场景中。
更多推荐

所有评论(0)