# 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 // 批量执行命令 await commandManager.executeBatch(commands: Command[], batchName?: string): Promise // 撤销/重做 await commandManager.undo(): Promise await commandManager.redo(): Promise // 状态管理 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 await compositeCommand.undo(): Promise // 获取信息 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` 的自定义命令 - **需要值传递**: 在自定义复合命令中处理命令间的数据依赖 - **错误处理**: 根据业务需求在复合命令中实现相应的错误处理策略