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

下面是一个完整的、严格模式下的 TypeScript 实现,包含 CommandManager、Command 和 CompositeCommand 类,支持 undo/redo 功能。

完整实现代码

1. Command 接口和基类

TypeScript 复制代码
// src/commands/ICommand.ts
export interface ICommand {
    execute(): void;
    undo(): void;
    redo(): void;
}
TypeScript 复制代码
// 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 实现

TypeScript 复制代码
// 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 实现

TypeScript 复制代码
// 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;
    }
}

示例使用代码

TypeScript 复制代码
// 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 包含严格模式设置:

TypeScript 复制代码
{
  "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 项目中。

相关推荐
huangql5202 小时前
从零到一打造前端内存监控 SDK,并发布到 npm ——基于 TypeScript + Vite + ECharts的解决方案
前端·typescript·echarts
zhennann2 小时前
VonaJS多租户同时支持共享模式和独立模式
数据库·typescript·node.js·nestjs
Z_Wonderful5 小时前
同时使用ReactUse 、 ahooks与性能优化
react.js·性能优化·typescript
PaoloBanchero10 小时前
Unity 虚拟仿真实验中设计模式的使用 ——命令模式(Command Pattern)
unity·设计模式·命令模式
椒盐螺丝钉14 小时前
TypeScript类型兼容性
运维·前端·typescript
烛阴1 天前
【TS 设计模式完全指南】构建你的专属“通知中心”:深入观察者模式
javascript·设计模式·typescript
濮水大叔1 天前
VonaJS多租户🔥居然可以同时支持共享模式和独立模式,太牛了🚀
typescript·node.js·nestjs
濮水大叔1 天前
VonaJS多租户同时支持共享模式和独立模式
typescript·nodejs·nestjs
科技林总2 天前
【TS3】搭建本地开发环境
学习·typescript