【问题标题】:Create a generic Factory class创建一个通用工厂类
【发布时间】:2022-01-27 07:35:42
【问题描述】:

我有几个工厂类做同样的事情:

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. 实例化对象并返回它

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

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

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 静态成员不能引用类类型参数:这是一个小问题,但我很想知道一个解决方法。 *2 找不到名称“T”:有没有办法实例化这个类?我知道 typescript 无法将其翻译成 js,我只是想写一些东西来表达我的意图。

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

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

【问题讨论】:

    标签: typescript oop typescript-generics


    【解决方案1】:

    *1 静态成员不能引用类类型参数。

    这就像询问印刷机的作者是谁。出版社印刷了许多书籍,所有这些书籍都有不同的作者。该请求没有意义。

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


    我认为您在这里想要的是静态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() 还接受一个构造函数,它将获取转换的输出并返回一个实例。我们从构造函数中捕获作为泛型的实例类型以设置返回类型。

    现在来测试一下:

    // 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


    说了这么多……你为什么要在这里使用一个类?如果它是一个纯函数,这将简单。实现是 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

    【讨论】:

    • 这是一个很好的答案。一旦 OOP 背景较重的人意识到大多数事情作为函数更容易,我觉得很多代码会变得更简单。
    • 哇,非常感谢!我什至不知道你可以在函数上使用泛型。你在这里教会了我很多。为了回答你为什么选择OOP方法的问题是我正在做的项目是OO,当我偶然发现这个问题时,我什至没有考虑功能解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多