Typescript - Store Class Type As Variable In Order to Create Objects From It Or Other Equivalent Functionality
Asked Answered
G

2

8

Preface - I have looked at similar posts to this one on SO, but the answers did not seem applicable.

I have an abstract class with the following method (simplified):

playAnimation() {
    let animator =  this.createAnimator(this.animatorData)  
    await animator.animate()
}

Create animator has the following definition inside that abstract class:

abstract createAnimator(animatorData: Object): Animator

A subclass might implement createAnimator like this:

createAnimator(animatorData: StandardAnimatorData) {
    return new RiverAnimator(animatorData.addMessage, animatorData.assetsDir) 
}

or like this:

createAnimator(animatorData: StandardAnimatorData) {
    return new BridgeAnimator(animatorData.addMessage, animatorData.assetsDir) 
}

As you can see - both of the sub classes's implementations of createAnimator are roughly identical, except for the type of the Animator being returned.

Sidenote BridgeAnimator and RiverAnimator both implement the Animator interface, but if the solution requires Animator to be an abstract class, I can change it.

Is there a way to move createAnimator into the abstract base class?

Ideally the abstract class would have an abstract variable that is the type of the class that createAnimator should return. Subclasses would merely implement that variable. Then createAnimator would use that variable to return the proper type of Animator.

Greyson answered 16/8, 2018 at 23:59 Comment(0)
S
6

Yes, you can do something like this (I made one big code snippet so I can verify that everything compiles):

interface Animator {
  animate(): Promise<void>;
}

class RiverAnimator implements Animator {
  async animate() { }
  constructor(addMessage: unknown, assetsDir: unknown) { }
}
class BridgeAnimator implements Animator {
  async animate() { }
  constructor(addMessage: unknown, assetsDir: unknown) { }
}

interface StandardAnimatorData {
  addMessage: unknown;
  assetsDir: unknown;
}

// Common interface for the constructor functions RiverAnimator and BridgeAnimator 
interface AnimatorConstructor {
  new(addMessage: unknown, assetsDir: unknown): Animator;
}

abstract class AbstractOuterClass {
  abstract animatorConstructor: AnimatorConstructor;
  animatorData: StandardAnimatorData;
  createAnimator(data: StandardAnimatorData) {
    return new this.animatorConstructor(data.addMessage, data.assetsDir);
  } 
  async playAnimation() {
    let animator = this.createAnimator(this.animatorData);
    await animator.animate();
  }
}

class SubOuterClass1 extends AbstractOuterClass {
  animatorConstructor = RiverAnimator;
}

class SubOuterClass2 extends AbstractOuterClass {
  animatorConstructor = BridgeAnimator;
}

But it's not clear to me that this is any better than implementing createAnimator in SubOuterClass1 and SubOuterClass2.

Slipperwort answered 17/8, 2018 at 0:28 Comment(0)
F
5

Another approach is to use the power of generics:

declaration:

createAnimator<T extends Animator>(
    type: { new(addMessage: unknown, assetDir: unknown): T }, 
    animatorData: StandardAnimatorData
): T {
    return new type(animatorData.addMessage, animatorData.assetDir);
}

invocation:

let animator = this.createAnimator(BridgeAnimator, animatorData);
await animator.animate();
Felicidad answered 28/2, 2020 at 14:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.