Hade0011
Hade0011

Reputation: 111

How to have a function in an abstract class return instances of a child classes in typescript

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

Answers (2)

ford04
ford04

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

ecraig12345
ecraig12345

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

Related Questions