创建通用工厂 class

Create a generic Factory class

我有几个 Factory classes 做同样的事情:

export class XFactory {
  private readonly validator: XValidator;
  private readonly transformer: XTransformer;

  private constructor( private readonly data: any, connection: Connection) {
    this.validator = new XValidator(connection);
    this.transformer = new XTransformer();
  }

  public static async build(data: any, connection: Connection) {
    const factory = new XFactory (connection);

    await factory.validate();

    const dto = await factory.transform();
    const x = new X(dto, connection);

    return x;
  }

  private async validate(): Promise<void | never> {
    await this.validator.validate(this.data);
  }

  private async transform(): Promise<XDto> {
    return await this.transformer.transform(this.data);
  }
}

过程总是一样的:

  1. 验证数据,如果无效则抛出错误
  2. 将数据转换为构造函数使用的有效 DTO
  3. 实例化对象并return它

我在创建此 class 的抽象时遇到问题:

我试过这样的父工厂,但我有两个问题,标有星号:

export class Factory<T,U> {
  private constructor( 
    private readonly data: any,
    private readonly validator: Validator,
    private readonly transformer: Transformer<U>
    ) {}

  protected static 
  public static async build(data: any, validator: Validator, transformer: Transformer<U>*1, connection: Connection) {
    const factory = new Factory (data, validator, transformer);

    await factory.validate();

    const dto: U*1 = await factory.transform();
    const x: T*1 = new T*2(...[dto, connection]);

    return x;
  }

  private async validate(): Promise<void|never> {
    await this.validator.validate(this.data);
  }

  private async transform(): Promise<XDto> {
    return await this.transformer.transform(this.data);
  }
}

*1 静态成员无法引用 class 类型参数:这是一个小问题,但我很想知道解决方法。 *2 找不到名称'T':有没有办法实例化这个class?我知道打字稿无法将其翻译成 js,我只是想写一些让我的意图明确的东西。

此外,构造函数方法是私有的,因为我不想将验证和转换与构造函数分开,但它们具有异步性质。

¿是否有另一种方法可以解决此问题,或者可以修改我的代码以便重构那些 XFactory classes 吗?

*1 Static members cannot reference class type parameters.

这就像问印刷机的作者是谁一样。出版社印了很多书,每本书都有不同的作者。该请求没有意义。

class 类型参数是针对每个实例的。不同的实例可能有不同的类型参数。除了约定之外,静态方法与 class 没有内在关系。它只是一个对相关功能进行分组的命名空间。只有按照惯例,这些函数才会执行与实例相关的任务。


我想你在这里想要的是静态 build 方法是通用的,因此它可以创建具有正确类型的 Factory 实例。然后你可以简单地使用那个实例。

// Make up some types to support what follows.
type Validator<Data> = { validate(data: Data): Promise<boolean> }
type Transformer<Data, Dto> = { transform(data: Data): Dto }
type Connection = { isConnected: boolean }

export class Factory<Data, Dto> {
    private constructor( 
        private readonly data: Data,
        private readonly validator: Validator<Data>,
        private readonly transformer: Transformer<Data, Dto>
    ) {}

    public static async build<Data, Dto, DtoInstance>(
        data: Data,
        validator: Validator<Data>,
        transformer: Transformer<Data, Dto>,
        dtoConstructor: new (dto: Dto, connection: Connection) => DtoInstance,
        connection: Connection
    ): Promise<DtoInstance> {
        const factory = new Factory(data, validator, transformer);
        await factory.validate();
        const dto = await factory.transform();
        return new dtoConstructor(dto, connection);
    }

    private async validate(): Promise<void|never> {
        await this.validator.validate(this.data);
    }

    private async transform(): Promise<Dto> {
        return await this.transformer.transform(this.data);
    }
}

我已将此处的 TU 分别重命名为 DataDtoData 是输入格式,Dto 是转换的输出。

现在请注意 build 函数也是通用的。它接受自己的泛型类型参数(我使用了相同的名称,但它们完全没有联系)。这允许在调用 build() 时指定 Factory 构造函数的参数,然后设置实例的类型。

最后,build() 还接受一个构造函数,它将采用转换的输出和 return 某物的实例。我们从构造函数中捕获的实例类型作为泛型来设置 return 类型。

现在进行测试:

// mock a connection
const connection = { isConnected: true }

class TestDto {
    public finalNum: number
    constructor(data: { finalNum: number }, connection: Connection) {
        this.finalNum = data.finalNum
    }
}

async function test() {
    const testDto = await Factory.build(
        { srcNum: 123 },
        { validate: async (data) => data.srcNum > 0 },
        { transform: (data) => ({ finalNum: data.srcNum }) },
        TestDto,
        connection
    )
    console.log(testDto) // TestDto: { finalNum: 123 }
    console.log(testDto.finalNum) // 123
}

test()

Playground


综上所述...您为什么要在这里使用 class?如果它是一个纯函数,这将 far 简单。实现是3条短线。

async function build<Data, Dto, DtoInstance>({
    data,
    validate,
    transform,
    dtoConstructor,
    connection
}: {
    data: Data,
    validate: (data: Data) => Promise<void>,
    transform: (data: Data) => Dto,
    dtoConstructor: new (dto: Dto, connection: Connection) => DtoInstance,
    connection: Connection,
}): Promise<DtoInstance> {
    await validate(data);
    const dto = transform(data);
    return new dtoConstructor(dto, connection);
}

你会这样使用:

// Make a reusable function bound to a specific Dto.
// Or maybe add as a static method of `TestDto`, `TestDto.build()`?
async function buildTestDto(data: { srcNum: number }) {
    return await build({
        data,
        validate: async (data) => {
            if (data.srcNum < 0) throw new Error("value must be positive")
        },
        transform: (data) => ({ finalNum: data.srcNum }),
        dtoConstructor: TestDto,
        connection,
    })
}

async function go() {
    const testDto = await buildTestDto({ srcNum: 123 })
    console.log(testDto) // TestDto: { finalNum: 123 }
    console.log(testDto.finalNum) // 123

    const invalid = await buildTestDto({ srcNum: -456 }) // throws
    console.log(invalid)
}
go()

Playground