为使用泛型类型的typeof值的函数定义接口

时间:2020-03-11 22:30:55

标签: typescript

我正在创建一个实例化对象的工厂,而不会丢失任何类型推断。

要实例化的对象类在键集合中定义:

abstract class Model { }

class Player extends Model {
    a: string;

    constructor(a: string) {
        super();

        this.a = a;
    }

    say() {
        console.log(this.a)
    }
}

class OtherModel extends Model {}

enum ModelType {
    Player,
    OtherModel
}

const Models = {
    [ModelType.Player]: Player,
    [ModelType.OtherModel]: OtherModel
};

最后,我想到的工厂的定义如下:

class ModelFactory implements IFactory {
    instantiate<T extends keyof typeof Models>(type: T, ...params: ConstructorParameters<typeof Models[T]>): InstanceType<typeof Models[T]> {
        if (Models) {
            const ModelRef = Models[type] as new (...params: any[]) => InstanceType<typeof Models[T]>;

            if (!ModelRef) {
                throw new Error(`Model for '${ModelType[type]}' was not found.`);
            }

            return new ModelRef(...params);
        }

        throw new Error("No models are registered");
    }
}

这很好用,因为它能够在编译时推断构造函数的属性以及返回类型。我遇到的问题是为工厂定义接口。由于我得到的是上面定义的模型集合的类型,因此无法提出定义抽象的Instantiate()签名的方法。

我创建了一个playground来更好地说明问题。

1 个答案:

答案 0 :(得分:2)

如果您只想让IFactory有一个instantiate方法,该方法恰好适用于Models对象中的构造函数,那么您可以为其赋予与相应对象相同的签名ModelFactory中的方法并完成。

尽管您希望某些东西对于具有构造函数的给定对象具有一个适用于该对象的相应 instantiate方法。如果是这样,则应将IFactory设置为generic接口,具体取决于构造函数对象的类型。这是我的写法:

interface IFactory<M extends Record<keyof M, new (...args: any) => any>> {
    instantiate<K extends keyof M>(
        type: K,
        ...params: ConstructorParameters<M[K]>
    ): InstanceType<M[K]>
}

在此代码中,M代替了typeof Models,并且是constrained到其对象是构造函数的对象类型。

然后您可以说ModelFactory implements IFactory<typeof Models>

class ModelFactory implements IFactory<typeof Models> {
    instantiate<T extends keyof typeof Models>(type: T, ...params: ConstructorParameters<typeof Models[T]>): InstanceType<typeof Models[T]> {
        if (Models) {
            const ModelRef = Models[type] as new (...params: any[]) => InstanceType<typeof Models[T]>;

            if (!ModelRef) {
                throw new Error(`Model for '${ModelType[type]}' was not found.`);
            }

            return new ModelRef(...params);
        }

        throw new Error("No models are registered");
    }
}

并且可能会声明要实现IFactory<typeof somethingElse>的工厂,其中somethingElse是持有不同构造函数的不同对象。


还请注意,您可以像这样使整个工厂类通用:

class GenericModelFactory<
    M extends Record<keyof M, new (...args: any) => any>
    > implements IFactory<M> {
    constructor(public models: M) {

    }
    instantiate<K extends keyof M>(
        type: K,
        ...params: ConstructorParameters<M[K]>
    ): InstanceType<M[K]> {
        const ModelRef = this.models[type] as
            new (...params: ConstructorParameters<M[K]>) => InstanceType<M[K]>;
        return new ModelRef(...params);

    }
}    

const modelFactory2 = new GenericModelFactory(Models);
const player2 = modelFactory2.instantiate(ModelType.Player, 'a', 'b');
player2.say();

这会将构造函数保留对象作为工厂类本身的属性,因此您可以重用同一工厂类,而不是为每组模型构造函数创建新的工厂类。这完全取决于您的用例,您想采用哪种方式。


无论如何,希望能有所帮助。祝你好运!

Playground link to code