I have an abstract base type and a bunch of subtypes of it. Some of the subtypes are abstract on their own and have further subtypes.
I'd like to be able to define polymorphic function, some of which may be abstracts, while others have a default implementation that could be overloaded by some nodes.
What's the best way to model this in TypeScript?
In JavaScript I would use classes. This is a silly, simple example which shows what I would write. Please don't worry about the semantics, just about the sort of data that I'm trying to model.
class Polygon {
constructor( name ) { this.name = name; }
// abstract methods
area() { assert.fail(`${this.constructor.name} must implement area()`); }
// methods with fallback implementation
toString() {
const props = Object.entries(this)
.filter( ([k, v])=>k !== 'name' )
.map( ([k, v])=>`${k}:${v}` )
.join(", ");
return `${this.name}{props}`;
}
}
class Triangle extends Shape {
constructor( base, height ) {
super('TRIANGLE');
this.base = base;
this.height = height;
}
area() { return this.base*this.height/2; }
}
class Quadrilateral extends Shape {
constructor( name, base, height ) {
super(name);
this.base = base;
this.height = height;
}
area() { return this.base*this.height; }
}
class Square extends Quadrilateral {
constructor( side ) {
super( 'SQUARE', side, side );
}
area() { return this.base*this.height; }
toString() { return `${this.name}{side:${this.base}}`; }
}
However converting this thing in TypeScript the naive way doesn't work too well:
The field
namecannot be used as a discriminant forPolygon. If I want to use a proper discriminant I have to introduce:type PolygonType = Triangle | Square;and usePolygonTypeinstead ofPolygonas the type for unknown subtypes of it.Using
classes limits what I can express. For instance I couldn't use type level metaprogramming to help me define the exact shape of my types.
A more TypeScript-friendly implementation would be modeling my types using types. But that would mean giving up polymorphism via prototype inheritance, as well as all the other perks of JavaScript classes.
I wonder whether there are other solutions to a similar problem, which I expect to be extremely common, and whether any is generally preferred.
It seems using interfaces instead of types would be beneficial in this case.
First of all, interfaces specify types, so the goal will be achieved.
On the other hand, interface literally and historically is an abstract class without single implemented methods.
So, if it is a class, it can be extended for polymorphic purposes in the same way as class does:
You also can use base interface for dependency inversion.