如何创建可访问其创建的类类型的静态成员的通用工厂方法

时间:2019-07-04 22:47:25

标签: typescript typescript-generics

我正在使用Typescript在Firebase上将REST API端点编写为Function,并且这些方法都遵循类似的模式:检查request.body,将我们的主体数据中的适当数据放入,类型对象,使用该对象通过一些数据访问代码将数据推送到数据库。多次编写相同的基本数据提取逻辑以处理request.body之后,我认为必须有一种抽象这项工作的方法。我对此有三个要求:(1)该方法应该可以从我的任何数据模型的request.body中提取数据。 (2)数据模型应该是完全自描述的,这样它们不仅可以描述数据应具有的属性,而且可以在需要特定属性集时关联。 (3)该方法应该能够从数据模型中判断出需要哪些属性,并对通过request.body传递的数据进行一些验证。

作为#2的示例,并且模型具有自我描述性:例如,考虑到当我创建新的数据记录时,我不需要ID,因为如果没有ID,则可以创建它在函数中,并将其传回。另一方面,在这种情况下,需要“名称”属性 。相比之下, update 方法需要记录ID(因此它知道要更新的记录),但是不需要则需要“名称”,除非实际上是这样改性。

我的方法是(1)在一个单独的类上使用静态工厂方法,该方法采用需要创建的数据模型的类类型;预期的操作(即 create read update delete );和请求正文。 (2)一组数据模型类,基本上只描述数据,并在需要时包括一些验证逻辑,但还包括(静态)字段名称和相关要求值的列表(存储为四位,每个位置代表一个) (四个CRUD操作中的一个。)(3)一个公共接口,以便静态工厂方法知道如何处理不同的数据对象以获取那些字段名称和使用标志。

这是我的静态工厂方法:

static create<T extends typeof DataObjectBase>(cls: { new(...args: any[]): T; }, intendedOperation: number, requestBody: any) : T {
        let dataObject : T = null;
        const sourceData = {};
        const objFields = cls.fieldNames;
        const flagCollection = cls.requiredUseFlags();
        const requiredFields = flagCollection.getFieldsForOperation(intendedOperation);
        if (requestBody) {
            // parse the request body
            // first get all values that are available and match object field names
            const allFields = Object.values(objFields); // gets all properties as key/value pairs for easier iteration
            // iterate through the allFields array
            for (const f in allFields) {
                if (requestBody.hasOwnProperty(f)) {
                    // prop found; add the field to 'sourceData' and copy the value from requestBody
                    sourceData[f] = requestBody[f];
                } else if (requiredFields.indexOf(f)>-1) {
                    // field is required but not available; throw error
                    throw new InvalidArgumentError(`${cls}.${f} is a required field, but no value found for it in request.body.`, requestBody);
                }
            }
            dataObject = (<any>Object).assign(dataObject, sourceData);
        } else {
            throw new ArgumentNullError('"requestBody" argument cannot be null.', requestBody);
        }
        return new cls();
    }

这是数据模型类的示例:

export class Address extends DataObjectBase {
    constructor(
        public id         : string,
        public street1    : string,
        public street2    : string = "",
        public city       : string,
        public state      : string,
        public zip        : string) {
        // call base constructor
        super();
    }

    static fieldNames = {
        ID      = "id",
        STREET1 = "street1",
        STREET2 = "street2",
        // you get the idea...
    }

    static requiredUseFlags() {
        ID = READ | UPDATE | DELETE,
        STREET1 = 0,
        // again, you get the idea...
        // CREATE, READ, UPDATE, DELETE are all bit-flags set elsewhere
    }
}

我希望能够像上面这样调用上面的create方法:

const address = create<Address>(Address, CREATE, request.body);

最初,我尝试过这样的签名:

static create<T extends typeof DataObjectBase>(cls: T, intendedOperation: number, requestBody: any) : T

但是,当我这样做时,出现一个错误:“地址是一种类型,但被用作值”。一旦将其更改为上面的内容,我就停止出现该错误并开始获取 Property 'fieldNames' does not exist on type 'new (...args: any[]) => T'

注意:我还尝试了使用两个接口来描述(在第一个中)实例方法和(在第二个中)静态方法的技巧,然后使静态接口扩展实例接口和基础类实现静态接口等,如hereherehere所述。这还没有完全到达我那里。

我当然愿意承认我可能对这一切进行了过度设计,并且我很乐意接受其他更简单的建议。但是我认为必须有一种方法来完成我想要的事情,并且避免一遍又一遍地编写相同的基本请求主体解析代码。

1 个答案:

答案 0 :(得分:2)

您可以在静态方法内使用./myprog 来引用当前类(使您能够编写this来创建该类的实例)。

关于以既可以构造对象又可以访问静态方式的方式键入此内容,最简单的解决方案是在定义时具有构造函数签名,并使用与new this()。这将保留静态成员,但删除基类的所有构造函数签名。

Pick<typeof DataObjectBase, keyof typeof DataObjectBase>也应该扩展T(实例类型)而不是DataObjectBase(类的类型)

typeof DataObjectBase

注意:只修复TS,不要对过度设计的部分发表意见