Reputation: 36189
Let's say I have
abstract class Foo {
}
class Bar1 extends Foo {
constructor(someVar) { ... }
}
class Bar2 extends Foo {
constructor(someVar) { ... }
}
I'd like to be able to create a static method that creates an instance of the final class (all constructors would have the same signature). So I want something like:
abstract class Foo {
public static someAction(someVar) {
const self = new this(someVar);
}
}
But this cannot be done because Foo
is abstract. Is this at all possible?
UPDATE
What if these classes have their own templates?
abstract class Foo<M> {
}
class Bar1 extends Foo<SomeModel> {...}
Now I want the someAction
to know of the type SomeModel
. I tried
public static someAction<A, T extends Foo<A>>(this: new (someVar: any) => T, someVar: any): T {
const self = new this(someVar);
return self;
}
But unless I specifically do Bar1.someAction<SomeModel>("blah")
the returned result is not available of the type of the data, i.e. Bar1.someAction("blah")
doesn't know the data type
Upvotes: 10
Views: 8567
Reputation: 1371
It think this problem should be managed with the factory method pattern, and move the static method from the abstract class to the factory class.
So then you will have:
So starting the same way as you had,
there is an abstract class with the shared data,
abstract class Foo{
constructor(someVar: any){
...
}
}
and then there are some classes that extend the abstract class,
class Bar1 extends Foo {
constructor(someVar: any){
super(someVar)
}
}
class Bar2 extends Foo {
constructor(someVar: any){
super(someVar)
}
}
to create each of those classes based on parameters, it's easy to have a factory with the creational logic:
class BarFactory {
public static barCreator(someVar: any): Bar1 | Bar2 {
if(someVar.x === 'whatever'){
return new Bar1(someVar)
} else {
return new Bar2(someVar)
}
}
}
At last you just get and use the instance using the factory.
const bar = BarFactory.barCreator(someVar)
All abstract properties and methods are directly available.
But f you want to use specific methods or properties of the instantiated class then you will need to check the instance type.
if (bar instanceof Bar1){
...
} else {
...
}
Upvotes: 1
Reputation: 323
After some monkeying around I found a way to address your problem with generics.
The key is to not use a type variable in the extends constraint for the base class but to add the base class with the type variable as an intersection of the return type.
Use any
instead of T
here static someAction<A, T extends Foo<any>>
and to add & Foo<A>
on the return type.
abstract class Foo<M> {...}
class Bar1 extends Foo<SomeModel> {...}
public static someAction<A, T extends Foo<any>>(
this: new (someVar: any) => T, someVar: any
): T & Foo<A> {
return new this(someVar);
}
I was facing the exact same issue while trying to create a user defined type guard, and the same technique above also solve this problem.
public static isThisType<A, T extends Foo<any>>(
this: new (...args: any) => T, value: unknown
): value is T & Foo<A> {
return value instanceof this;
}
Upvotes: 0
Reputation: 249506
You can add an annotation for the this
type of the static method. In this case this
will refer to the class, and adding an annotation for the this
parameter will make the static method visible only on classes that satisfy the constraint (in this case that a constructor with a single argument exists) and it will also help us extract the actual instance type of the class the method is invoked on :
abstract class Foo {
public static someAction<T extends Foo>(this: new (someVar: any) => T, someVar: any): T {
const self = new this(someVar);
return self;
}
}
class Bar1 extends Foo {
constructor(someVar: any) {
super();
}
}
class Bar2 extends Foo {
constructor(someVar: any) {
super();
}
}
let bar1 = Bar1.someAction(0) // of type Bar1
let bar2 = Bar2.someAction(0) // of type Bar2
Upvotes: 20