最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Typescript static method in an abstract class to create an instance of self - Stack Overflow

programmeradmin6浏览0评论

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

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

Share Improve this question edited Sep 27, 2018 at 0:10 Kousha asked Sep 26, 2018 at 8:21 KoushaKousha 36.2k59 gold badges186 silver badges313 bronze badges 2
  • Did you ever figure out the answer to your updated question? – Tom Daniel Commented Dec 30, 2020 at 1:46
  • That's exactly the problem I have – EuberDeveloper Commented Apr 4, 2022 at 21:27
Add a comment  | 

3 Answers 3

Reset to default 20

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

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:

  • a factory class with the static method responsible for creating the specific classes.
  • an abstract class with the shared data, logic and the required abstract components to implement.
  • any extending classes with implementation of the corresponding abstract properties and methods from the parent class.

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 {
  ...
}

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;
}
发布评论

评论列表(0)

  1. 暂无评论