/** * Mason 2019-09-29: This is one way to allow subclasses to inherit a * method with a parameter whose type refers to the actual subclass type * (and not the parent class's type). * * One purpose here is to define a base class, that can have various * subclasses, and enable all of the subclasses to inherit a class method * named configure(), which creates an instance of the subclass and then * configures the instance and its various subclass-specific properties. * * This gets weird in TypeScript, due to polymorphic 'this' not being * available for static methods and constructors. This implementation was * derived from the comment thread at: * * https://github.com/microsoft/TypeScript/issues/5863 * * (That thread also shows several of the ways that trying to do this * kind of thing can fail to work in TypeScript, which may be surprising.) * * This implementation uses the "fake this parameter" described here: * * https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#specifying-the-type-of-this-for-functions * * Combined with `InstanceType` this allows us to capture the type in * such a way that the configurator function works in subclasses, and the * subclass properties can be auto-completed, etc. */ class Configurable { static configure(this: T, configurator: ((x: InstanceType) => void)): InstanceType { const result = (new this()) as InstanceType; configurator(result); return result; } } class Person extends Configurable { name = "Alice"; age = 0; get bio() { return `My name is ${this.name}, and I am a ${this.constructor.name}. I am ${this.age} years old.`; } } class FireFighter extends Person { helmetSize?: number; hasLicenseToDriveFireEngine = false; } class PoliceOfficer extends Person { badgeNumber = "0000000"; } const rand = Person.configure( x => { x.age = 117; x.name = "Rand"; }); const jane = PoliceOfficer.configure( x => { x.name = "Jane"; x.age = 42; x.badgeNumber = "8675309"; }); const configureBiff = (x: FireFighter) => { x.hasLicenseToDriveFireEngine = true; x.helmetSize = 15; x.age = 21; x.name = "Biff Wigginberger Jr."; }; const biff = FireFighter.configure(configureBiff); console.log(rand.bio); console.log(jane.bio); console.log(biff.bio); // prints: // My name is Rand, and I am a Person. I am 117 years old. // My name is Jane, and I am a PoliceOfficer. I am 42 years old. // My name is Biff Wigginberger Jr., and I am a FireFighter. I am 21 years old.