Typescript 命令模式 return 类型

Typescript Command pattern return type

下面是命令模式,您有一个命令和一个命令的处理程序,它们被添加到 CommandBus,然后在被调用时在那里执行。在这种情况下,该命令具有一种类型 <string>,我希望在 CommandBus

中执行命令时对其进行 returned

问题是最后一行 var whatIsThis = CommandBus.execute<string>(new GetStringCommand("Hello, world")); 更具体地说,我想从该语句中删除,因为它应该来自 CommandCommandBus class 应该从 class GetStringCommand implements Command<string> 知道它应该 return。

interface Command<T> {
    name:string;
}

class GetStringCommand implements Command<string> {
    public str:string;
    public name:string = "GetStringCommand";
    constructor(str:string){
        this.str = str;
    }
}

interface CommandHandler<T> {
    execute(command:Command<T>): T;
}

class GetStringHandler implements CommandHandler<string> {
    execute(command:GetStringCommand):string {
        return command.str;
    }
}

interface CommandRegistry {
    [x:string]: CommandHandler<any>
}

class CommandBus {
    private static actions:CommandRegistry = {};

    static add(name:string, command:CommandHandler<any>) {
        CommandBus.actions[name] = command;
    }

    static execute<T>(command:Command<T>) : T {
        return CommandBus.actions[command.name].execute(command);
    }
}

CommandBus.add("GetStringCommand", new GetStringHandler());

var whatIsThis = CommandBus.execute<string>(new GetStringCommand("Hello, world"));

上面的解决方案是有效的并且运行良好,但它是一个糟糕的解决方案,因为它使重构变得痛苦,并且让我不得不一遍又一遍地重复自己,因为我将使用数千个命令。

实例 这是在 MongoDB 中保存模式的命令处理程序的示例,可以使用相同的命令在 MySQL

中创建模式
export class SaveSchemaCommandHandler implements CommandHandlerBase<void> {
    execute(command:SaveSchemaCommand) {
        var db = MongoConnection.db;
        var collectionOptions = { autoIndexID: true };

        db.createCollection(command.schema.getName(), collectionOptions);
    }
}

据我了解,您的处理程序是真正的命令,而您所谓的命令只是处理程序执行所需的参数。

您可以用简单的界面替换您的 "commands",并且您的处理程序成为命令。
然后,您可以删除这些新命令对 类 的需要,因为您可以将执行函数作为命令传递。

像这样:

interface CommandData {}

interface GetStringCommandData extends CommandData {
    value: string;
}

interface SaveSchemaCommandData extends CommandData {
    schema: { name: string };
}

type Command<In extends CommandData, Out> = (data: In) => Out;

interface CommandRegistry {
    [x: string]: Command<CommandData, any>;
}

class CommandBus {
    private static actions:CommandRegistry = {};

    static add(name: string, command: Command<CommandData, any>) {
        CommandBus.actions[name] = command;
    }

    static execute<T>(name: string, data: CommandData) : T {
        return CommandBus.actions[name](data);
    }
}

CommandBus.add("GetStringCommand", (data: GetStringCommandData) => data.value);
CommandBus.add("SaveSchemaCommand", (data: SaveSchemaCommandData) => {
    let db = MongoConnection.db;
    let collectionOptions = { autoIndexID: true };

    db.createCollection(data.schema.name, collectionOptions);
});

CommandBus.execute("GetStringCommand", { value: "my string" });
CommandBus.execute("SaveSchemaCommand", { schema: { name: "mySchema" } });

看起来更简单,更容易维护,但我不确定它是否符合您的所有需求。


编辑

从执行操作中获取正确的类型并非易事,但您有几个选择:

(1) 在 CommandData 中使用 return 类型泛型:

interface CommandData<T> {}

interface GetStringCommandData extends CommandData<string> {
    value: string;
}

class CommandBus {
    ...

    static execute<T>(name: string, data: CommandData<T>): T {
        return CommandBus.actions[name](data);
    }
}

let str: string = CommandBus.execute("GetStringCommand", { value: "my string" } as CommandData<string>);

(2) 使用 any:

class CommandBus {
    ...

    static execute(name: string, data: CommandData): any {
        return CommandBus.actions[name](data);
    }
}

let str: string = CommandBus.execute("GetStringCommand", { value: "my string" });

(3) 声明执行的可能签名:

class CommandBus {
    ...

    static execute(name: "GetStringCommand", data: GetStringCommandData): string;
    static execute(name: "SaveSchemaCommand", data: SaveSchemaCommandData): void;
    static execute(name: string, data: CommandData): any;
    static execute(name: string, data: CommandData): any {
        return CommandBus.actions[name](data);
    }
}

let str: string = CommandBus.execute("GetStringCommand", { value: "my string" });

这个选项的可扩展性不是很好,但它仍然是一个选项。