Home > other >  How to declare the different types correctly for fabric method in typescript
How to declare the different types correctly for fabric method in typescript

Time:11-25

I try to use Fabric method for creating two kind of concrete products which have different config in a constructor. TS Interpreter gets me an error about config, because, as I see it tries to mix them, but the problem is they are different for each product and I'm explicitly pointing them out as - type ConfigI = ConfigProduct1 | ConfigProduct2 What Im doing wrong ? Here is link to playground.

type ConfigProduct1 = {
    arg1: string
}

type ConfigProduct2 = {
    arg2: string
}

type ConfigI = ConfigProduct1 | ConfigProduct2

enum TYPE {
    PRODUCT1,
    PRODUCT2
}

class Creator {
    static createProduct(type: TYPE, config: ConfigI) {
        switch(type) {
            case TYPE.PRODUCT1:
                return new ConcreteProduct1(config)
            case TYPE.PRODUCT2:
                return new ConcreteProduct2(config)
            default:
                throw new Error('Wrong Product type')
        }
    }
}

abstract class Product {
    protected config: ConfigI
    get cfg() {
        return this.config
    }
    constructor(config: ConfigI) {
        this.config = config
    }

    abstract foo(): void
}

class ConcreteProduct1 extends Product {
    foo() {
        console.log(this.config.arg1) // here is mistake it can't find arg2 in config
    }
}

class ConcreteProduct2 extends Product {
    foo() {
        console.log(this.config.arg2) // here is mistake it can't find arg1 in config
    }
}

const product = Creator.createProduct(TYPE.PRODUCT1, {arg1: 'something'})

Moreover, if I try to use product as a link in an another class method Interpreter gets me an error what argument does not exists at all in product config, e.g.

// next one
class ProductUsing {
    protected product: Product
    constructor(product: Product) {
        this.product = product
    }

    bar() {
        console.log(this.product.cfg.arg1) // here an error - Property 'arg1' does not exist on type 'ConfigI'.
    }
}

CodePudding user response:

I have fixed your code here:

type ConfigProduct1 = {
    cfg1: string
}

type ConfigProduct2 = {
    cfg2: string
}

abstract class Product<T extends ConfigI> {
    protected config: T

    constructor(config: T) {
        this.config = config
    }

    abstract foo(): void
}

class ConcreteProduct1 extends Product<ConfigProduct1> {
    foo() {
        console.log(this.config.cfg1)
    }
}

class ConcreteProduct2 extends Product<ConfigProduct2> {
    foo() {
        console.log(this.config.cfg2)
    }
}

type ConfigI = ConfigProduct1 | ConfigProduct2

enum TYPE {
    PRODUCT1,
    PRODUCT2
}

const map = {
    [TYPE.PRODUCT1]: ConcreteProduct1,
    [TYPE.PRODUCT2]: ConcreteProduct2,
}

class Creator {
    static createProduct<T extends keyof typeof map>(type: T, config: InstanceType<typeof map[T]> extends Product<infer U> ? U : never): InstanceType<typeof map[T]> {
        return new (map[type] as any)(config);
    }
}

const product = Creator.createProduct(TYPE.PRODUCT1, { cfg1: 'somethere' });

The key elements:

  • You need a runtime construct in order to create the mapping between the enum and the classes. I've called it map
  • The return value of createProduct has to be casted as any in the function implementation because the parameter types are generic. They are not resolved inside the function implementation, so TypeScript cannot match the class and its constructor parameter type. It only does on the function call and that's why Creator.createProduct(TYPE.PRODUCT1, { cfg1: 'somethere' }) works fine.
  • Related