727 lines
20 KiB
Markdown
727 lines
20 KiB
Markdown
|
|
# CommandManager 命令管理器使用指南
|
|||
|
|
|
|||
|
|
## 📖 概述
|
|||
|
|
|
|||
|
|
CommandManager 是一个基于经典命令模式的撤销/重做系统,提供队列机制防止快速执行时命令丢失,支持复合命令进行批量操作,确保了高性能和稳定性。
|
|||
|
|
|
|||
|
|
## ✨ 核心特性
|
|||
|
|
|
|||
|
|
- **撤销/重做**: 基于经典命令模式的撤销重做功能
|
|||
|
|
- **队列机制**: 防止快速执行多个命令时的命令丢失
|
|||
|
|
- **串行化执行**: 确保命令按顺序执行,避免并发问题
|
|||
|
|
- **Promise支持**: 完全支持异步命令
|
|||
|
|
- **性能监控**: 可选的性能统计功能
|
|||
|
|
- **复合命令**: 支持批量操作和命令组合
|
|||
|
|
- **状态管理**: 实时状态监控和回调
|
|||
|
|
|
|||
|
|
## 🎯 复合命令详解
|
|||
|
|
|
|||
|
|
### 什么是复合命令?
|
|||
|
|
|
|||
|
|
复合命令是一组相关操作的集合,这些操作作为一个整体执行,支持命令间的值传递和条件执行。在Canvas编辑器中,复合命令特别适用于:
|
|||
|
|
|
|||
|
|
- 创建复杂组件(同时创建多个对象和图层)
|
|||
|
|
- 批量修改属性
|
|||
|
|
- 导入文件(可能涉及多个图层和对象的创建)
|
|||
|
|
- 复制粘贴操作
|
|||
|
|
|
|||
|
|
### 复合命令的执行流程
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TD
|
|||
|
|
A[创建复合命令] --> B[添加子命令]
|
|||
|
|
B --> C[执行复合命令]
|
|||
|
|
C --> D[串行执行子命令]
|
|||
|
|
D --> E{执行成功?}
|
|||
|
|
E -->|是| F[记录已执行命令]
|
|||
|
|
F --> G[继续下一个命令]
|
|||
|
|
G --> D
|
|||
|
|
E -->|否| H[回滚已执行命令]
|
|||
|
|
H --> I[抛出错误]
|
|||
|
|
|
|||
|
|
C --> J[撤销复合命令]
|
|||
|
|
J --> K[逆序撤销子命令]
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚀 快速开始
|
|||
|
|
|
|||
|
|
### 1. 基础批量操作
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { CommandManager } from './CommandManager.js';
|
|||
|
|
import { Command } from '../../commands/Command.js';
|
|||
|
|
|
|||
|
|
// 创建命令管理器
|
|||
|
|
const commandManager = new CommandManager({
|
|||
|
|
maxHistorySize: 50,
|
|||
|
|
performanceManager: null
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 创建简单命令
|
|||
|
|
class SetPropertyCommand extends Command {
|
|||
|
|
constructor(target, property, newValue) {
|
|||
|
|
super({ name: '设置属性', description: `设置${property}为${newValue}` });
|
|||
|
|
this.target = target;
|
|||
|
|
this.property = property;
|
|||
|
|
this.newValue = newValue;
|
|||
|
|
this.oldValue = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
execute() {
|
|||
|
|
this.oldValue = this.target[this.property];
|
|||
|
|
this.target[this.property] = this.newValue;
|
|||
|
|
return { property: this.property, oldValue: this.oldValue, newValue: this.newValue };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
undo() {
|
|||
|
|
this.target[this.property] = this.oldValue;
|
|||
|
|
return { property: this.property, restoredValue: this.oldValue };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用批量操作
|
|||
|
|
const obj = { x: 0, y: 0, color: 'black' };
|
|||
|
|
|
|||
|
|
// 方法1: 使用 executeBatch
|
|||
|
|
await commandManager.executeBatch([
|
|||
|
|
new SetPropertyCommand(obj, 'x', 100),
|
|||
|
|
new SetPropertyCommand(obj, 'y', 200),
|
|||
|
|
new SetPropertyCommand(obj, 'color', 'red')
|
|||
|
|
], '批量设置对象属性');
|
|||
|
|
|
|||
|
|
console.log(obj); // { x: 100, y: 200, color: 'red' }
|
|||
|
|
|
|||
|
|
// 撤销时,所有属性会一次性恢复
|
|||
|
|
await commandManager.undo();
|
|||
|
|
console.log(obj); // { x: 0, y: 0, color: 'black' }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 创建自定义复合命令
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
import { CompositeCommand } from '../../commands/Command.js';
|
|||
|
|
|
|||
|
|
// 支持值传递的复合命令
|
|||
|
|
class CreateLayerWithObjectsCommand extends CompositeCommand {
|
|||
|
|
constructor(layerName, objects) {
|
|||
|
|
super([], { name: '创建图层并添加对象' });
|
|||
|
|
this.layerName = layerName;
|
|||
|
|
this.objects = objects;
|
|||
|
|
this.layerId = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
// 先创建图层
|
|||
|
|
const createLayerCmd = new CreateLayerCommand(this.layerName);
|
|||
|
|
this.layerId = await createLayerCmd.execute();
|
|||
|
|
this.addCommand(createLayerCmd);
|
|||
|
|
|
|||
|
|
// 使用返回的 layerId 添加对象
|
|||
|
|
for (const obj of this.objects) {
|
|||
|
|
const addObjectCmd = new AddObjectCommand(obj, this.layerId);
|
|||
|
|
await addObjectCmd.execute();
|
|||
|
|
this.addCommand(addObjectCmd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { layerId: this.layerId, objectCount: this.objects.length };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用自定义复合命令
|
|||
|
|
const objects = [object1, object2, object3];
|
|||
|
|
const command = new CreateLayerWithObjectsCommand('新图层', objects);
|
|||
|
|
const result = await commandManager.execute(command);
|
|||
|
|
console.log(`创建了图层 ${result.layerId},包含 ${result.objectCount} 个对象`);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 高级功能
|
|||
|
|
|
|||
|
|
### 1. 条件执行的复合命令
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ConditionalSetupCommand extends CompositeCommand {
|
|||
|
|
constructor(config) {
|
|||
|
|
super([], { name: '条件设置' });
|
|||
|
|
this.config = config;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
// 总是创建基础对象
|
|||
|
|
const createCmd = new CreateObjectCommand(this.config.object);
|
|||
|
|
await createCmd.execute();
|
|||
|
|
this.addCommand(createCmd);
|
|||
|
|
|
|||
|
|
// 根据条件决定是否创建图层
|
|||
|
|
if (this.config.shouldCreateLayer) {
|
|||
|
|
const layerCmd = new CreateLayerCommand(this.config.layerName);
|
|||
|
|
await layerCmd.execute();
|
|||
|
|
this.addCommand(layerCmd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 根据条件应用样式
|
|||
|
|
if (this.config.applyStyle) {
|
|||
|
|
const styleCmd = new ApplyStyleCommand(this.config.style);
|
|||
|
|
await styleCmd.execute();
|
|||
|
|
this.addCommand(styleCmd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { commandCount: this.commands.length };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 错误处理和回滚
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class RobustBatchCommand extends CompositeCommand {
|
|||
|
|
constructor(operations) {
|
|||
|
|
super([], { name: '健壮的批量操作' });
|
|||
|
|
this.operations = operations;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
try {
|
|||
|
|
for (const operation of this.operations) {
|
|||
|
|
const command = new operation.CommandClass(...operation.args);
|
|||
|
|
|
|||
|
|
// 执行命令
|
|||
|
|
await command.execute();
|
|||
|
|
this.addCommand(command);
|
|||
|
|
|
|||
|
|
console.log(`✅ 操作成功: ${command.constructor.name}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { success: true, executedCount: this.commands.length };
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 批量操作失败:', error);
|
|||
|
|
|
|||
|
|
// 自动回滚已执行的命令
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 状态监控
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 设置状态变化回调
|
|||
|
|
commandManager.setChangeCallback((state) => {
|
|||
|
|
console.log('命令管理器状态:', {
|
|||
|
|
canUndo: state.canUndo,
|
|||
|
|
canRedo: state.canRedo,
|
|||
|
|
undoCount: state.undoCount,
|
|||
|
|
redoCount: state.redoCount,
|
|||
|
|
isExecuting: state.isExecuting,
|
|||
|
|
lastCommand: state.lastCommand
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 获取命令历史
|
|||
|
|
const history = commandManager.getHistory();
|
|||
|
|
console.log('撤销历史:', history.undoHistory);
|
|||
|
|
console.log('重做历史:', history.redoHistory);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 💡 实际应用场景
|
|||
|
|
|
|||
|
|
### 1. 复杂UI组件创建
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class CreateComplexCardCommand extends CompositeCommand {
|
|||
|
|
constructor(canvas, config) {
|
|||
|
|
super([], { name: '创建复杂卡片' });
|
|||
|
|
this.canvas = canvas;
|
|||
|
|
this.config = config;
|
|||
|
|
this.createdObjects = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
try {
|
|||
|
|
// 创建背景
|
|||
|
|
const background = await this._createBackground();
|
|||
|
|
const bgCmd = new AddObjectCommand(background);
|
|||
|
|
await bgCmd.execute();
|
|||
|
|
this.addCommand(bgCmd);
|
|||
|
|
|
|||
|
|
// 创建头部
|
|||
|
|
const header = await this._createHeader();
|
|||
|
|
const headerCmd = new AddObjectCommand(header);
|
|||
|
|
await headerCmd.execute();
|
|||
|
|
this.addCommand(headerCmd);
|
|||
|
|
|
|||
|
|
// 创建内容区域
|
|||
|
|
const content = await this._createContent();
|
|||
|
|
const contentCmd = new AddObjectCommand(content);
|
|||
|
|
await contentCmd.execute();
|
|||
|
|
this.addCommand(contentCmd);
|
|||
|
|
|
|||
|
|
// 创建按钮
|
|||
|
|
const buttons = await this._createButtons();
|
|||
|
|
for (const button of buttons) {
|
|||
|
|
const buttonCmd = new AddObjectCommand(button);
|
|||
|
|
await buttonCmd.execute();
|
|||
|
|
this.addCommand(buttonCmd);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { success: true, objectCount: this.commands.length };
|
|||
|
|
} catch (error) {
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _createBackground() {
|
|||
|
|
// 创建背景逻辑
|
|||
|
|
return new fabric.Rect({
|
|||
|
|
width: this.config.width,
|
|||
|
|
height: this.config.height,
|
|||
|
|
fill: this.config.backgroundColor
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ...existing code...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 批量导入处理
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class ImportMultipleImagesCommand extends CompositeCommand {
|
|||
|
|
constructor(files, layerManager) {
|
|||
|
|
super([], { name: `导入${files.length}个图片文件` });
|
|||
|
|
this.files = files;
|
|||
|
|
this.layerManager = layerManager;
|
|||
|
|
this.importedLayers = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
try {
|
|||
|
|
for (const file of this.files) {
|
|||
|
|
// 加载图片
|
|||
|
|
const image = await this._loadImageFromFile(file);
|
|||
|
|
|
|||
|
|
// 创建图层
|
|||
|
|
const createLayerCmd = new CreateLayerCommand(file.name);
|
|||
|
|
const layerId = await createLayerCmd.execute();
|
|||
|
|
this.addCommand(createLayerCmd);
|
|||
|
|
|
|||
|
|
// 添加对象到图层
|
|||
|
|
const addObjectCmd = new AddObjectToLayerCommand(image, layerId);
|
|||
|
|
await addObjectCmd.execute();
|
|||
|
|
this.addCommand(addObjectCmd);
|
|||
|
|
|
|||
|
|
this.importedLayers.push(layerId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { importedLayers: this.importedLayers };
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('批量导入失败:', error);
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _loadImageFromFile(file) {
|
|||
|
|
return new Promise((resolve, reject) => {
|
|||
|
|
const reader = new FileReader();
|
|||
|
|
reader.onload = (e) => {
|
|||
|
|
fabric.Image.fromURL(e.target.result, resolve);
|
|||
|
|
};
|
|||
|
|
reader.onerror = reject;
|
|||
|
|
reader.readAsDataURL(file);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用
|
|||
|
|
const files = [file1, file2, file3];
|
|||
|
|
const importCmd = new ImportMultipleImagesCommand(files, layerManager);
|
|||
|
|
const result = await commandManager.execute(importCmd);
|
|||
|
|
console.log('导入的图层:', result.importedLayers);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 复制粘贴操作
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class CopyPasteObjectsCommand extends CompositeCommand {
|
|||
|
|
constructor(sourceObjects, targetPosition) {
|
|||
|
|
super([], { name: '复制粘贴对象' });
|
|||
|
|
this.sourceObjects = sourceObjects;
|
|||
|
|
this.targetPosition = targetPosition;
|
|||
|
|
this.copiedObjects = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
try {
|
|||
|
|
for (const obj of this.sourceObjects) {
|
|||
|
|
// 克隆对象
|
|||
|
|
const clonedObj = await this._cloneObject(obj);
|
|||
|
|
|
|||
|
|
// 设置新位置
|
|||
|
|
clonedObj.set({
|
|||
|
|
left: this.targetPosition.x + (clonedObj.left - this.sourceObjects[0].left),
|
|||
|
|
top: this.targetPosition.y + (clonedObj.top - this.sourceObjects[0].top)
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 添加到画布
|
|||
|
|
const addCmd = new AddObjectCommand(clonedObj);
|
|||
|
|
await addCmd.execute();
|
|||
|
|
this.addCommand(addCmd);
|
|||
|
|
|
|||
|
|
this.copiedObjects.push(clonedObj);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { copiedObjects: this.copiedObjects };
|
|||
|
|
} catch (error) {
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _cloneObject(obj) {
|
|||
|
|
return new Promise((resolve) => {
|
|||
|
|
obj.clone(resolve);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🛠️ 错误处理策略
|
|||
|
|
|
|||
|
|
### 1. 复合命令中的错误处理
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 推荐的错误处理模式
|
|||
|
|
class SafeBatchCommand extends CompositeCommand {
|
|||
|
|
constructor(operations, options = {}) {
|
|||
|
|
super([], { name: options.name || '安全批量操作' });
|
|||
|
|
this.operations = operations;
|
|||
|
|
this.stopOnError = options.stopOnError !== false; // 默认遇到错误就停止
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
const results = [];
|
|||
|
|
const errors = [];
|
|||
|
|
|
|||
|
|
for (let i = 0; i < this.operations.length; i++) {
|
|||
|
|
const operation = this.operations[i];
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const command = new operation.CommandClass(...operation.args);
|
|||
|
|
const result = await command.execute();
|
|||
|
|
|
|||
|
|
this.addCommand(command);
|
|||
|
|
results.push({ index: i, success: true, result });
|
|||
|
|
|
|||
|
|
console.log(`✅ 操作 ${i + 1}/${this.operations.length} 成功`);
|
|||
|
|
} catch (error) {
|
|||
|
|
errors.push({ index: i, error: error.message });
|
|||
|
|
console.error(`❌ 操作 ${i + 1}/${this.operations.length} 失败:`, error);
|
|||
|
|
|
|||
|
|
if (this.stopOnError) {
|
|||
|
|
// 回滚已执行的命令
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw new Error(`批量操作在第 ${i + 1} 个操作时失败: ${error.message}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return {
|
|||
|
|
results,
|
|||
|
|
errors,
|
|||
|
|
successCount: results.length,
|
|||
|
|
errorCount: errors.length
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 超时处理
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class TimeLimitedCommand extends CompositeCommand {
|
|||
|
|
constructor(operations, timeout = 5000) {
|
|||
|
|
super([], { name: '限时批量操作' });
|
|||
|
|
this.operations = operations;
|
|||
|
|
this.timeout = timeout;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
const timeoutPromise = new Promise((_, reject) => {
|
|||
|
|
setTimeout(() => reject(new Error('操作超时')), this.timeout);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const executePromise = this._executeOperations();
|
|||
|
|
const result = await Promise.race([executePromise, timeoutPromise]);
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return result;
|
|||
|
|
} catch (error) {
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _executeOperations() {
|
|||
|
|
for (const operation of this.operations) {
|
|||
|
|
const command = new operation.CommandClass(...operation.args);
|
|||
|
|
await command.execute();
|
|||
|
|
this.addCommand(command);
|
|||
|
|
}
|
|||
|
|
return { success: true, operationCount: this.operations.length };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📊 最佳实践
|
|||
|
|
|
|||
|
|
### 1. 命令粒度控制
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// ✅ 好的命令粒度 - 逻辑相关的操作组合
|
|||
|
|
class CreateUserProfileCommand extends CompositeCommand {
|
|||
|
|
constructor(userData) {
|
|||
|
|
super([], { name: '创建用户档案' });
|
|||
|
|
this.userData = userData;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
const avatarCmd = new CreateAvatarCommand(this.userData.avatar);
|
|||
|
|
await avatarCmd.execute();
|
|||
|
|
this.addCommand(avatarCmd);
|
|||
|
|
|
|||
|
|
const nameCmd = new CreateNameLabelCommand(this.userData.name);
|
|||
|
|
await nameCmd.execute();
|
|||
|
|
this.addCommand(nameCmd);
|
|||
|
|
|
|||
|
|
const infoCmd = new CreateInfoPanelCommand(this.userData.info);
|
|||
|
|
await infoCmd.execute();
|
|||
|
|
this.addCommand(infoCmd);
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return { success: true };
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ❌ 过大的命令粒度 - 不相关的操作混合
|
|||
|
|
// 应该拆分成多个独立的命令
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 命名规范
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// ✅ 清晰的命令命名
|
|||
|
|
await commandManager.executeBatch([...], '上传并创建图片图层');
|
|||
|
|
new CreateLayerWithObjectsCommand('新图层', objects);
|
|||
|
|
new ImportMultipleImagesCommand(files, layerManager);
|
|||
|
|
|
|||
|
|
// ❌ 模糊的命令命名
|
|||
|
|
await commandManager.executeBatch([...], '操作');
|
|||
|
|
new BatchCommand();
|
|||
|
|
new ProcessCommand();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 性能优化
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 对于大量操作,考虑分批处理
|
|||
|
|
class BatchProcessor {
|
|||
|
|
constructor(commandManager, batchSize = 10) {
|
|||
|
|
this.commandManager = commandManager;
|
|||
|
|
this.batchSize = batchSize;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async processBatch(items, createCommand, progressCallback) {
|
|||
|
|
const batches = this._createBatches(items, this.batchSize);
|
|||
|
|
|
|||
|
|
for (let i = 0; i < batches.length; i++) {
|
|||
|
|
const batch = batches[i];
|
|||
|
|
const commands = batch.map(item => createCommand(item));
|
|||
|
|
|
|||
|
|
await this.commandManager.executeBatch(
|
|||
|
|
commands,
|
|||
|
|
`批处理 ${i + 1}/${batches.length}`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 更新进度
|
|||
|
|
if (progressCallback) {
|
|||
|
|
const progress = ((i + 1) / batches.length) * 100;
|
|||
|
|
progressCallback(progress);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_createBatches(items, batchSize) {
|
|||
|
|
const batches = [];
|
|||
|
|
for (let i = 0; i < items.length; i += batchSize) {
|
|||
|
|
batches.push(items.slice(i, i + batchSize));
|
|||
|
|
}
|
|||
|
|
return batches;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用示例
|
|||
|
|
const processor = new BatchProcessor(commandManager, 10);
|
|||
|
|
await processor.processBatch(
|
|||
|
|
largeItemList,
|
|||
|
|
(item) => new ProcessItemCommand(item),
|
|||
|
|
(progress) => console.log(`处理进度: ${progress.toFixed(1)}%`)
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔧 API 参考
|
|||
|
|
|
|||
|
|
### CommandManager 核心方法
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 执行单个命令
|
|||
|
|
await commandManager.execute(command: Command): Promise<any>
|
|||
|
|
|
|||
|
|
// 批量执行命令
|
|||
|
|
await commandManager.executeBatch(commands: Command[], batchName?: string): Promise<any>
|
|||
|
|
|
|||
|
|
// 撤销/重做
|
|||
|
|
await commandManager.undo(): Promise<any>
|
|||
|
|
await commandManager.redo(): Promise<any>
|
|||
|
|
|
|||
|
|
// 状态管理
|
|||
|
|
commandManager.getState(): ManagerState
|
|||
|
|
commandManager.getHistory(): HistoryInfo
|
|||
|
|
commandManager.clear(): void
|
|||
|
|
|
|||
|
|
// 回调设置
|
|||
|
|
commandManager.setChangeCallback(callback: (state: ManagerState) => void): void
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### CompositeCommand 核心方法
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 添加子命令
|
|||
|
|
compositeCommand.addCommand(command: Command): CompositeCommand
|
|||
|
|
compositeCommand.addCommands(commands: Command[]): CompositeCommand
|
|||
|
|
|
|||
|
|
// 执行和撤销
|
|||
|
|
await compositeCommand.execute(): Promise<any>
|
|||
|
|
await compositeCommand.undo(): Promise<any>
|
|||
|
|
|
|||
|
|
// 获取信息
|
|||
|
|
compositeCommand.getInfo(): CommandInfo
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🚨 常见问题
|
|||
|
|
|
|||
|
|
### Q1: 什么时候使用 executeBatch,什么时候创建自定义复合命令?
|
|||
|
|
|
|||
|
|
A:
|
|||
|
|
- **使用 executeBatch**: 当命令之间没有数据依赖,只是简单的批量执行时
|
|||
|
|
- **创建自定义复合命令**: 当需要命令间值传递、条件执行或复杂逻辑时
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 简单批量 - 使用 executeBatch
|
|||
|
|
await commandManager.executeBatch([
|
|||
|
|
new SetPropertyCommand(obj, 'x', 100),
|
|||
|
|
new SetPropertyCommand(obj, 'y', 200)
|
|||
|
|
], '批量设置属性');
|
|||
|
|
|
|||
|
|
// 复杂逻辑 - 创建自定义复合命令
|
|||
|
|
class CreateLayerWithObjectsCommand extends CompositeCommand {
|
|||
|
|
// 需要使用前一个命令的返回值
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q2: 复合命令执行失败时会自动回滚吗?
|
|||
|
|
|
|||
|
|
A: 是的,`CompositeCommand` 在执行失败时会自动回滚已执行的子命令。
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 如果第3个命令失败,前2个命令会被自动撤销
|
|||
|
|
const commands = [command1, command2, failingCommand, command4];
|
|||
|
|
try {
|
|||
|
|
await commandManager.executeBatch(commands);
|
|||
|
|
} catch (error) {
|
|||
|
|
// 此时 command1 和 command2 已被自动撤销
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q3: 如何处理部分命令失败的情况?
|
|||
|
|
|
|||
|
|
A: 创建自定义复合命令,设置错误处理策略:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
class TolerantBatchCommand extends CompositeCommand {
|
|||
|
|
constructor(commands, options = {}) {
|
|||
|
|
super([], { name: '容错批量命令' });
|
|||
|
|
this.originalCommands = commands;
|
|||
|
|
this.continueOnError = options.continueOnError || false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async execute() {
|
|||
|
|
const results = [];
|
|||
|
|
|
|||
|
|
for (const cmd of this.originalCommands) {
|
|||
|
|
try {
|
|||
|
|
await cmd.execute();
|
|||
|
|
this.addCommand(cmd);
|
|||
|
|
results.push({ success: true, command: cmd.constructor.name });
|
|||
|
|
} catch (error) {
|
|||
|
|
results.push({ success: false, error: error.message });
|
|||
|
|
|
|||
|
|
if (!this.continueOnError) {
|
|||
|
|
await this._rollbackExecutedCommands();
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.executedCommands = [...this.commands];
|
|||
|
|
return results;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Q4: 如何优化大量命令的性能?
|
|||
|
|
|
|||
|
|
A: 使用分批处理和进度反馈:
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// 分批处理大量命令
|
|||
|
|
const BATCH_SIZE = 20;
|
|||
|
|
const batches = [];
|
|||
|
|
|
|||
|
|
for (let i = 0; i < largeCommandList.length; i += BATCH_SIZE) {
|
|||
|
|
batches.push(largeCommandList.slice(i, i + BATCH_SIZE));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const [index, batch] of batches.entries()) {
|
|||
|
|
await commandManager.executeBatch(batch, `批次 ${index + 1}/${batches.length}`);
|
|||
|
|
|
|||
|
|
// 给UI更新的机会
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎯 总结
|
|||
|
|
|
|||
|
|
移除事务机制后的 CommandManager 更加简洁和灵活:
|
|||
|
|
|
|||
|
|
1. **简化架构**: 去除了不必要的事务状态管理
|
|||
|
|
2. **更好的数据流**: 复合命令支持命令间值传递
|
|||
|
|
3. **灵活的错误处理**: 自定义复合命令可以实现各种错误处理策略
|
|||
|
|
4. **更清晰的API**: `executeBatch` 用于简单批量,自定义复合命令用于复杂逻辑
|
|||
|
|
5. **更好的性能**: 减少了状态管理开销
|
|||
|
|
|
|||
|
|
推荐的使用模式:
|
|||
|
|
- **简单批量操作**: 使用 `commandManager.executeBatch()`
|
|||
|
|
- **复杂业务逻辑**: 创建继承自 `CompositeCommand` 的自定义命令
|
|||
|
|
- **需要值传递**: 在自定义复合命令中处理命令间的数据依赖
|
|||
|
|
- **错误处理**: 根据业务需求在复合命令中实现相应的错误处理策略
|