dimanche 12 novembre 2017

TypeScript Factory Subclass is not Assignable to Type Superclass

I want to use the Factory Pattern using TypeScript to convert raw data into a User. I have two different types of Users: Employee and Customer. Both extend the base class and compile without problems. Below is an extremely simplified version of these three classes.

abstract class User {
  constructor(data: any) { }

  static createFromServer<T extends User>(this: new (...args: any[]) => T, data: any): T {
    return new this(data);
  }
}

class Employee extends User {
  static '@type' = 'Employee';

  static createFromServer<T extends User>(this: new (...args: any[]) => T, data: any): T {
    return new this(data);
  }
}

class Customer extends User {
  static '@type' = 'Customer';

  static createFromServer<T extends User>(this: new (...args: any[]) => T, data: any): T {
    return new this(data);
  }
}

Simple enough. Now for the Factory. I want to create a Factory that will take raw data returned from the server and convert that data into something the rest of the system can use (i.e. an instance of User). What I want to do is something like

class UserFactory {
  /**
   * Vends a User constructed from the raw data from the server.
   *
   * @param userData (Object) - the User definition, as obtained from the server
   *
   * @return (User) the User initialized from `userData`
   */
  static vend<T extends User>(userData: any): T {
    switch (userData['@type']) {
      case Employee['@type']:
        return Employee.createFromServer(userData);

      case Customer['@type']:
        return Customer.createFromServer(userData);

      default:
        throw new Error('Unknown User type.');
    }
  }
}

As you can probably guess (since I'm here and not relaxing over the weekend), that doesn't work. I get the following error:

Type 'Employee' is not assignable to type 'T'.

Since T extends User and Employee extends User without adding or removing anything (in this simplified version), it seems like the above should work. What's more annoying is it seems the above will work in other languages (e.g. Java (tested), C++), I'm just not sure why it won't work in this situation.

Somebody on GitHub suggested that the problem might be with overloading the constructor, but clearly that's not occurring here.

So, why isn't Employee assignable to type T? (How) can I get the above to work? Is there a better way to handle this in TypeScript? A correct answer will answer at least the first 2 questions.





Aucun commentaire:

Enregistrer un commentaire