基于typescript严格模式以实现undo和redo功能为目标的命令模式代码参考

发布于:2025-07-18 ⋅ 阅读:(13) ⋅ 点赞:(0)

下面是一个完整的、严格模式下的 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 order
        for (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 command
        this._redoStack.length = 0;
        
        // Ensure we don't exceed max stack size
        if (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类,这两个类需要:

  1. 持有操作的目标对象和必要参数(通常通过构造函数注入)

  2. 实现具体的执行/撤销逻辑(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抽象类开始?主要是基于一下三点考虑:

  1. 允许未来添加不基于 Command 基类的其他命令实现

  2. 使 CommandManager 只依赖接口而不依赖具体实现(符合DIP原则)

  3. 为组合模式(如 CompositeCommand)提供了更清晰的类型约束

  • Command 抽象类

        基类 Command 额外提供了:执行状态跟踪_isExecuted防重复执行(通过检查状态),这使得子类可以更专注于业务逻辑,而不用重复编写状态管理代码。

  1. 实现了基础执行状态跟踪

  2. 要求子类实现实际执行和撤销逻辑

  • CompositeCommand

    • 可以组合多个命令一起执行

    • 撤销时按相反顺序执行

  • CommandManager

    • 管理 undo/redo 堆栈

    • 限制最大堆栈大小防止内存问题

    • 提供清晰的API和状态查询

这个实现是完全类型安全的,符合 TypeScript 严格模式要求,并且可以直接集成到 Vite 项目中。


网站公告

今日签到

点亮在社区的每一天
去签到