【问题标题】:Refer to an Abstract Class type for a factory function, can't create abstract class工厂函数引用抽象类类型,不能创建抽象类
【发布时间】:2021-05-27 15:35:06
【问题描述】:

我正在尝试设置一个工厂函数来创建抽象类的子类。 工厂函数应该与子类类型无关。

这是抽象类:

export abstract class AbstractWorkflowTrigger {
    protected params: any = null;
    protected configParams: any = null;
    protected user: any = null;
    protected expectedParams: any = {};

    constructor(params: any = {}, user?: any, configParams: any = {}) {
        this.params = {
            ...configParams,
            ...params
        };
        this.user = user;
        this.checkParams();
    }

    protected checkParams() {
        const missingParams = Object.keys(this.expectedParams).filter(
            (e: any) => this.expectedParams[e].required && !Object.keys(this.params).includes(e)
        );
        if (missingParams.length > 0) {
            throw Error(`Missing required param(s): ${missingParams.join(', ')}`);
        }
        for (const param in this.params) {
            if (this.params.hasOwnProperty(param)) {
                console.log(this.expectedParams);
                if (!this.expectedParams.hasOwnProperty(param)) {
                    throw Error(`Unknown param '${param}'`);
                }
                if (typeof this.params[param] !== this.expectedParams[param].type) {
                    throw Error(
                        `Wrong type for param '${param}', expected '${
                            this.expectedParams[param].type
                        }', got '${typeof this.params[param]}' instead`
                    );
                }
            }
        }
    }

    public abstract do(task: ITask): Promise<void>;
}

子类的例子:

class AddCommentWorkflowTrigger extends AbstractWorkflowTrigger {
    expectedParams = {
        comment: {
            required: false,
            type: 'string'
        },
        text: {
            required: true,
            type: 'string'
        }
    }
    public async do(task: ITask): Promise<void> {
        if (task.comments.length > 0) {
            task.comments += "\n";
        }
        if (this.params.comment) {
            task.comments += `${this.params.text} - ${this.params.comment}`  
        } else {
            task.comments += `${this.params.text}`;
        }

    }
}

我需要一个工厂函数来运行 checkParams() 函数,因为 expectedParams 成员没有设置为抽象类的构造函数上的子类值,我真的不想运行它每个子类上的 constructor() 函数以保持 DRY。

我目前拥有的工厂函数:

export function workflowTriggerFactory(c: any, ...params: any[]): AbstractWorkflowTrigger {
    const built = new c(params);
    c.checkParams();
    return built;
}

export const WorkflowTriggers = {
    assign: AssignWorkflowTrigger,
    setOwnershipToUser: SetOwnershipToUserWorkflowTrigger,
    unsetOwnership: UnsetOwnershipWorkflowTrigger,
    addComment: AddCommentWorkflowTrigger
};

应该是这样称呼的:

   await this.executeTriggersOnTask(category.actions[action].triggers, task, params, user);

以前的用法(无法将expectedParams获取到子类值):

   const action: AbstractWorkflowTrigger = new (WorkflowTriggers as any)[trigger](params, user);

我不确定如何为工厂函数键入 c 参数,因为我想保持类型安全,但使用 AbstractWorflowTrigger 不会让我在它上面运行 new

我应该选择什么签名? 当 Abstract 类运​​行 checkParams() 时,也许您会看到一种更好的方法来获得高效的代码,同时将 expectedParams 设置为正确的值?

【问题讨论】:

  • 我可能会为您提供解决方案,但我需要先在 TS 操场上闲逛一会儿。至于输入c 参数,您可以执行type Ctr&lt;T&gt; = new () =&gt; T 之类的操作吗?
  • 您能否解释一下paramsconfigParamsexpectedParams 在您想要实现的目标方面的区别
  • params 是来自 HTTP POST 请求的参数,configParams 是在 YAML 文件中设置的参数,expectedParams 是 TriggerAction 预期的参数(是否需要)。 type Ctr&lt;T&gt; = new () =&gt; T 在做什么?
  • 它定义了一个类型别名,给定一个泛型 T 类型可以使用 new 关键字实例化一个 T。试着把它放在文件的顶部,创建一个命名的空类,创建一个具有这种类型的变量,最后将该变量分配给命名的空类。您应该看到该类型捕获了实例化该类的能力。
  • 是派生类还是泛型类应该定义预期的参数?

标签: javascript typescript


【解决方案1】:

我不明白您要做什么,但我认为这应该可以满足您的需求,或者足够接近您可以对其进行修改以适合您的用例。除了我自己的编码偏好之外,我没有充分理由退出checkParams 函数,在我看来,最好有一个功能核心。这里的秘诀在于 TS 允许您从函数中返回类定义。我利用它来定义稍后可以由另一段代码实例化的类。这个类使用一些闭包魔法检查工厂提供的参数。

type Param = { required: boolean, type: string }
type Params = Record<string, Param>

function checkParams<T extends Object>(schema: Params, obj: T) {
    let schemaKeys = Object.keys(schema);
    let requiredSchemaKeys = schemaKeys.filter(key => schema[key].required);
    let objKeys = Object.keys(obj);
    let missingRequiredSchemaKeys = requiredSchemaKeys.filter(key => !objKeys.includes(key))
    let missingObjKeys = objKeys.filter(key => !schemaKeys.includes(key))

    if(missingRequiredSchemaKeys.length > 0) {
        throw Error(`Missing required param(s): ${missingRequiredSchemaKeys.join(', ')}`);
    }
    
    if(missingObjKeys.length > 0) {
        throw Error(`Unknown param(s): ${missingObjKeys.join(', ')}`);
    }
    
    for(let key of schemaKeys) {
        let param = schema[key];
        let value = (obj as any)[key];
        let actualType = typeof value;
        let expectedType = param.type;
        if(actualType !== expectedType) {
            throw Error(`Wrong type for param '${param}', expected '${expectedType}', got '${actualType}' instead`);
        }
    }
}

interface ITask {}

interface IWorkflow {
    expectedParams: Params
    params: Record<string, any>
    user: any
    run(task: ITask): Promise<void>
}

abstract class AbstractWorkflow implements IWorkflow {
    expectedParams: Params
    params: Record<string, any>
    user: any

    constructor(params: Record<string, any>, expectedParams: Params, user: any) {
        checkParams(expectedParams, params)
        this.params = params;
        this.expectedParams = expectedParams;
        this.user = user;
    }

    abstract run(task: ITask): Promise<void>
}

class WorkflowFactory {
    protected expectedParams: Params;

    constructor(expectedParams: Params = {}) {
        this.expectedParams = expectedParams;
    }

    workflow(name: string, runner: (this: IWorkflow, task: ITask) => Promise<void>): new (params: Record<string, any>, user: any) => IWorkflow {
        let self = this
        let clazz = class extends AbstractWorkflow {
            constructor(params: Record<string, any>, user: any) {
                super(params, self.expectedParams, user)
            }

            run(task: ITask): Promise<void> {
                return runner.call(this, task)
            }
        }
        
        Object.defineProperty(clazz, 'name', {
            writable: false,
            value: name
        })
        return clazz
    }
}

【讨论】:

  • 很有趣,但恐怕我不明白这里所做的一切。有没有办法以更简单的方式使用该 模板来使我的代码正常工作?
  • 我不确定我是否理解您的问题足以提出其他建议。您可以精简问题中的代码并隔离您想要的内容,或者您​​也可以给我一个 typescript.org/play 链接,该链接有一个明显的错误,然后我可以修复并发回
猜你喜欢
  • 1970-01-01
  • 2013-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多