基于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 项目中。

相关推荐
xqslsm12 小时前
TypeScript 联合类型深入解析
typescript
叫我少年14 小时前
TypeScript 基础类型
typescript
大爱编程♡17 小时前
Vue3+TypeScript+element-plus的文章管理项目(配后端接口)-退出及文章管理页面
前端·javascript·typescript
如此风景21 小时前
TS装饰器详解
typescript
人间凡尔赛1 天前
2026 年 React Server Components 完全指南:Next.js 16 全栈开发最佳实践
前端·typescript·react·全栈·next.js
晓说前端2 天前
TypeScript 核心语法进阶 —— 字面量类型与类型推论
前端·typescript
布兰妮甜2 天前
从 0 搭建企业级 Vue3/Vite 脚手架(规范、eslint、husky、打包、环境变量全流程)
typescript·vue3·vite·脚手架·前端工程化
东方小月2 天前
从0开发一个 Coding Agent(一):前言
前端·人工智能·typescript
gis开发之家3 天前
《Vue3 从入门到大神35篇》Vue3 源码详解(五):effect 依赖收集原理——track 与 trigger 是如何工作的?
javascript·typescript·前端框架·vue3·vue3源码
退休倒计时3 天前
【每日一题】LeetCode 131. 分割回文串 TypeScript
算法·leetcode·typescript