Pluto
Pluto

Reputation: 859

Call for a static function that exists in inheritance classes

Suppose I have this inheritance tree, with a static sample function that exists in the classes D.ts, E.ts, F.ts, G.ts:

enter image description here

I want the thisIsTestFunction () to be a static function and not an abstract function, I don't want this function appear in the bases classes (A, B, C)!

I created an array and I want to run the array in a loop and call the static function:

let objects : A = new Array(20);

for (const object of objects ) {
           // object.thisIsTestFunction(); ??
           // How call the function here ?
}

I try to call the function with object.constructor.name but it doesn't working.

I know that a static function can only be called using the class name, But abstract function is not a good solution for me, I want the function to appear only once in each class and be a static function.

Upvotes: 1

Views: 212

Answers (1)

jcalz
jcalz

Reputation: 329783

The main issue that prevents the compiler from understanding what you're doing is that the constructor property of an object is not strongly typed. See microsoft/TypeScript#3851 for more information. Right now, object.constructor is just typed as Function, which isn't known to have a thisisTestFunction() method. One thing you can do is manually give every concrete class an appropriate strongly-typed constructor property, but this is tedious (and has some gotchas about subclassing that I won't go into here):

class D extends C {
    public static thisisTestFunction() {
        alert("Testing");
    }    
    ["constructor"]: typeof D // here
}

And then if you had an object of, say, type D you could do this:

const d = new D();
d.constructor.thisisTestFunction(); // okay

Another problem is that with an array of objects of type A, the compiler has no way to know that each element will definitely be one of D, E, F, or G. TypeScript has no concept of a final class (see microsoft/TypeScript#5863), and even if it did it wouldn't necessarily be able to represent that the A hierarchy is "sealed":

class Oops extends A {
    ["constructor"]: typeof Oops;
    // no static function here
}

let someObjects: A[] = new Array(20);
someObjects.push(new Oops()); // what if the array contains an Oops?
for (const object of someObjects) {
    object.constructor.thisisTestFunction(); // still error
}

Instead, you might want to tell the compiler that your array contains elements not of type A, but of one of the particular concrete subclasses of A that you care about:

type ConcreteA = D | E | F | G;
let objects: ConcreteA[] = new Array(20);
objects.push(new Oops()); // error now to do this
for (const object of objects) {
    object.constructor.thisisTestFunction(); // okay
}

The ConcreteA type is the union of D, E, F, and G, and as long as each one of those has a strongly-typed constructor that knows about the thisisTestFunction() static method, then it will work without error.


As an aside, if the implementation of thisisTestFunction() is truly identical in each concrete subclass, you might consider putting that static function up in A anyway, and mark the method as requiring that its this context (as represented by a this parameter) correspond to a concrete constructor:

abstract class A {
    public static thisisTestFunction<T extends new (...args: any) => A>(this: T) {
        alert("Testing");
    }
}

This is less code, and TypeScript will complain if you try to call it on A, B, or C, but allow it on D, through G:

A.thisisTestFunction(); // error!
B.thisisTestFunction(); // error!
D.thisisTestFunction(); // okay

Okay, hope that helps; good luck!

Playground link to code

Upvotes: 2

Related Questions