下面是一個完整的、嚴格模式下的 TypeScript 實現,包含 CommandManager、Command 和 CompositeCommand 類,支持 undo/redo 功能。
完整實現代碼
1. Command 接口和基類
// src/commands/ICommand.ts
export interface ICommand {execute(): void;undo(): void;redo(): void;
}
// src/commands/Command.ts
import type{ ICommand } from "./ICommand";export abstract class Command implements ICommand {private _isExecuted: boolean = false;public execute(): void {if (!this._isExecuted) {this.doExecute();this._isExecuted = true;}}public undo(): void {if (this._isExecuted) {this.doUndo();this._isExecuted = false;}}public redo(): void {if (!this._isExecuted) {this.doExecute();this._isExecuted = true;}}protected abstract doExecute(): void;protected abstract doUndo(): void;public get isExecuted(): boolean {return this._isExecuted;}
}
2. CompositeCommand 實現
// src/commands/CompositeCommand.ts
import type{ ICommand } from "./ICommand";
import { Command } from "./Command";export class CompositeCommand extends Command {private readonly _commands: ICommand[] = [];public addCommand(command: ICommand): void {this._commands.push(command);}public removeCommand(command: ICommand): void {const index = this._commands.indexOf(command);if (index !== -1) {this._commands.splice(index, 1);}}public clearCommands(): void {this._commands.length = 0;}public get commands(): ReadonlyArray<ICommand> {return this._commands;}protected doExecute(): void {for (const command of this._commands) {command.execute();}}protected doUndo(): void {// Undo in reverse orderfor (let i = this._commands.length - 1; i >= 0; i--) {this._commands[i].undo();}}
}
3. CommandManager 實現
// src/commands/CommandManager.ts
import type{ ICommand } from "./ICommand";export class CommandManager {private readonly _undoStack: ICommand[] = [];private readonly _redoStack: ICommand[] = [];private readonly _maxStackSize: number;constructor(maxStackSize: number = 100) {this._maxStackSize = maxStackSize;}public execute(command: ICommand): void {command.execute();this._undoStack.push(command);// Clear redo stack when executing a new commandthis._redoStack.length = 0;// Ensure we don't exceed max stack sizeif (this._undoStack.length > this._maxStackSize) {this._undoStack.shift();}}public undo(): boolean {const command = this._undoStack.pop();if (command) {command.undo();this._redoStack.push(command);return true;}return false;}public redo(): boolean {const command = this._redoStack.pop();if (command) {command.redo();this._undoStack.push(command);return true;}return false;}public clearHistory(): void {this._undoStack.length = 0;this._redoStack.length = 0;}public get canUndo(): boolean {return this._undoStack.length > 0;}public get canRedo(): boolean {return this._redoStack.length > 0;}public get undoStackSize(): number {return this._undoStack.length;}public get redoStackSize(): number {return this._redoStack.length;}
}
示例使用代碼
// src/main.ts
import { CommandManager } from "./commands/CommandManager";
import { Command } from "./commands/Command";
import { CompositeCommand } from "./commands/CompositeCommand";// 示例命令 - 修改文本
class ChangeTextCommand extends Command {constructor(private _target: { text: string },private _newText: string,private _oldText: string) {super();}protected doExecute(): void {this._target.text = this._newText;console.log(`Text changed to: ${this._newText}`);}protected doUndo(): void {this._target.text = this._oldText;console.log(`Text reverted to: ${this._oldText}`);}
}// 示例命令 - 修改數字
class ChangeNumberCommand extends Command {constructor(private _target: { value: number },private _newValue: number,private _oldValue: number) {super();}protected doExecute(): void {this._target.value = this._newValue;console.log(`Number changed to: ${this._newValue}`);}protected doUndo(): void {this._target.value = this._oldValue;console.log(`Number reverted to: ${this._oldValue}`);}
}// 使用示例
const commandManager = new CommandManager();const textObject = { text: "Initial text" };
const numberObject = { value: 0 };// 創建并執行單個命令
const changeTextCommand = new ChangeTextCommand(textObject, "New text", textObject.text);
commandManager.execute(changeTextCommand);// 創建并執行組合命令
const compositeCommand = new CompositeCommand();
compositeCommand.addCommand(new ChangeTextCommand(textObject, "Composite text", textObject.text));
compositeCommand.addCommand(new ChangeNumberCommand(numberObject, 42, numberObject.value));
commandManager.execute(compositeCommand);// 測試 undo/redo
console.log("--- Undo ---");
commandManager.undo(); // 撤銷組合命令
console.log("Current text:", textObject.text);
console.log("Current number:", numberObject.value);console.log("--- Redo ---");
commandManager.redo(); // 重做組合命令
console.log("Current text:", textObject.text);
console.log("Current number:", numberObject.value);console.log("--- Undo single command ---");
commandManager.undo(); // 撤銷組合命令
commandManager.undo(); // 撤銷第一個文本修改命令
console.log("Current text:", textObject.text);
????????在以上示例代碼中,ChangeTextCommand類和ChangeNumberCommand類繼承了?Command
類,這兩個類需要:
持有操作的目標對象和必要參數(通常通過構造函數注入)
實現具體的執行/撤銷邏輯(
doExecute()
/doUndo()
)
而基類?Command
?則負責:
管理命令的執行狀態
提供執行流程的骨架
嚴格模式配置
確保你的?tsconfig.json
?包含嚴格模式設置:
{"compilerOptions": {"strict": true,"noImplicitAny": true,"strictNullChecks": true,"strictFunctionTypes": true,"strictBindCallApply": true,"strictPropertyInitialization": true,"noImplicitThis": true,"alwaysStrict": true}
}
設計說明
ICommand 接口:定義了命令模式的基本操作。?
??????這里需要說明一下,為什么先要定義一個ICommand接口,而不是直接從Command抽象類開始?主要是基于一下三點考慮:
允許未來添加不基于?
Command
?基類的其他命令實現使?
CommandManager
?只依賴接口而不依賴具體實現(符合DIP原則)為組合模式(如?
CompositeCommand
)提供了更清晰的類型約束
Command 抽象類:
????????基類?Command
?額外提供了:執行狀態跟蹤(_isExecuted
)防重復執行(通過檢查狀態),這使得子類可以更專注于業務邏輯,而不用重復編寫狀態管理代碼。
實現了基礎執行狀態跟蹤
要求子類實現實際執行和撤銷邏輯
CompositeCommand:
可以組合多個命令一起執行
撤銷時按相反順序執行
CommandManager:
管理 undo/redo 堆棧
限制最大堆棧大小防止內存問題
提供清晰的API和狀態查詢
這個實現是完全類型安全的,符合 TypeScript 嚴格模式要求,并且可以直接集成到 Vite 項目中。