user11406
user11406

Reputation: 1258

How would I enforce types with this factory pattern?

Here is a simple example of what I'm trying to achieve. In this example I have some animal classes and a Farm class which serves as a factory for a given animal type:

class Farm<T> {
    farmAnimal: any;
    animals: Array<T> = [];

    constructor(farmAnimal: new() => T, farmSize: number) {
        this.farmAnimal = farmAnimal;

        for(let i =0; i<farmSize; i++) {
            this.animals.push(new this.farmAnimal());
        }
    }
}

class Pig {
    doThing(){}
}

class Sheep {
    doThing(){}
}

This works, but my problem is I can do thing like this:

let farm = new Farm<Pig>(Sheep, 10);

The issue seems to be with the new() => T type in the Farm constructor not enforcing the type of constructor passed in.

Is there a better way to do this so TypeScript doesn't see new Farm<Pig>(Sheep, 10) as valid?

Example: https://www.typescriptlang.org/play?target=1#code/MYGwhgzhAEBiYCcC2AeAKgPmgbwLAChojoAzRJAQQDsBLJMEALmjCoE8BuA4l2+kCMwoIEYNuiwBeaAG0Aul3zdiwAPZUIAFwQBXYJtUIAFGWTU6DZlQCmAdyMBKaJKxoANKXIBlGgC9rVjpIAEbWCE54hDzEmgAWNBAAdKaUfAzOnmZpIIrK0aSGRiDWmtA0zgAMHGUoKT7+1TQA1E0Refkx8UmsFgKJAA46ELFGNrbQcQnJ5Ob8jg6KHdAAvu2r+OsEoJAwAAo0AOY47QAmqmjxVAeO2Oub+NtQ0F6x1tb9x1FEZxc0Vzd3AgEYqlTTWLQZMZwcgofYHDBGF5vfoeACMFQWBCAA

Upvotes: 1

Views: 41

Answers (2)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249536

The problem is that the two classes are structurally the same. Typescript uses structural typing, so if two types are structurally the same they are equivalent, this applies even to classes.

The simplest way to ensure that the classes are not aliasable is to add a private field to the. Even if the name is same, since it is private it will not be structurally equivalent.

class Factory<T> {
    private objs: Array<T> = [];
    private objClass: any;

    constructor(objClass: new () => T) {
        this.objClass = objClass;
        this.objs.push(new this.objClass());
    }
}


class Test{
    private _notAliased: undefined;
    test() {

    }
}

class Test2{
    private _notAliased: undefined;
    test() {
        
    }
}

let p1 = new Factory<Test>(Test2); // error now

Upvotes: 2

Titulum
Titulum

Reputation: 11456

As you can see from this typescript playground link, what you are saying is incorrect. You can not do new Farm<Pig>(Sheep, 10);.

Here is the code for when the typescript playground ever goes offline:

class Farm<T> {
  farmAnimal: any;
  animals: Array<T> = [];

  constructor(farmAnimal: new() => T, farmSize: number) {
    this.farmAnimal = farmAnimal;

    for(let i=0; i < farmSize; i++) {
      this.animals.push(new this.farmAnimal());
    }
  }
}

class Pig {
  doPigThing(): void {

  }
}

class Sheep {
  doSheepThing(): void {

  }
}

const animal = new Farm<Sheep>(Pig, 3); // Compiler error: 

The compiler error is:

Argument of type 'typeof Pig' is not assignable to parameter of type 'new () => Sheep'. Property 'doSheepThing' is missing in type 'Pig' but required in type 'Sheep'.

Upvotes: 2

Related Questions