Reputation: 111
Is it possible to have something like this pseudocode:
abstract class AbstractParent {
protected value: number;
constructor(value: number) {
this.value = value;
}
...
public times(multiplicator: number) {
return new ???(value * multiplicator);
}
}
Which returns a new Foo
for class Foo extends AbstractParent
and a new Bar
for class Bar extends AbstractParent
?
Upvotes: 2
Views: 3135
Reputation: 74620
A maintainable way is to keep the times
calculation in the base class, and implement separate create
factory methods inside Foo
and Bar
. This way, the base class is not aware of its extending child classes.
You declare create
as abstract
in AbstractParent
to make sure it will be implemented in all sub classes. The polymorphic this return type ensures for the caller of times
, that the returned instance has the same sub class type (Playground).
abstract class AbstractParent {
protected value: number;
constructor(value: number) {
this.value = value;
}
times(multiplicator: number) {
return this.create(this.value * multiplicator);
}
// factory method creates instances of same polymorphic type (Foo / Bar)
protected abstract create(value: number): this
}
class Foo extends AbstractParent {
create(value: number) {
// Polymorphic this type is Foo here, so it's OK to return a Foo.
// Compiler is not aware of the class context, so we cast.
return new Foo(value) as this
}
}
class Bar extends AbstractParent {
// same as Foo
create(value: number) {
return new Bar(value) as this
}
}
const foo = new Foo(42).times(2) // Foo
const bar = new Bar(42).times(3) // Bar
Upvotes: 2
Reputation: 2447
You could pass the child type into the parent constructor and save a reference. This prevents the parent having to know about all the possible child types (though I agree with the other answer that there may be a better way to achieve your overall goal). Something like this (playground link):
type ChildConstructor<Child> = new (value: number, childConstructor: ChildConstructor<Child>) => Child;
abstract class AbstractParent<Child extends AbstractParent<Child>> {
protected value: number;
protected childConstructor: ChildConstructor<Child>;
constructor(value: number, childConstructor: ChildConstructor<Child>) {
this.value = value;
this.childConstructor = childConstructor;
}
public times(multiplicator: number): Child {
return new this.childConstructor(this.value * multiplicator, this.childConstructor);
}
}
class Foo extends AbstractParent<Foo> {
constructor(value: number) {
super(value, Foo);
}
}
class Bar extends AbstractParent<Bar> {
constructor(value: number) {
super(value, Bar);
}
}
Note that to get 100% type safety, each child class would need to have at least one additional private property or method to keep TypeScript's "duck typing" from considering the classes to be interchangeable (since they share the same properties and methods).
Upvotes: 0