Typescript - Gem klasse-type som variabel for at kunne oprette objekter fra den eller anden tilsvarende funktionalitet


Forord - Jeg har kigget på lignende opslag som dette på SO, men svarene virkede ikke anvendelige.

Jeg har en abstrakt klasse med følgende metode (forenklet):

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

Create animator har følgende definition inde i den abstrakte klasse:

abstract createAnimator(animatorData: Object): Animator

En underklasse kan implementere createAnimator på denne måde:

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

eller sådan her:

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

Som du kan se - begge underklassers implementeringer af createAnimator er stort set identiske, bortset fra typen af Animator, der returneres.

Bemærk, at BridgeAnimator og RiverAnimator begge implementerer Animator-grænsefladen, men hvis løsningen kræver, at Animator skal være en abstrakt klasse, kan jeg ændre det.

Er der en måde at flytte createAnimator ind i den abstrakte baseklasse?

Ideelt set ville den abstrakte klasse have en abstrakt variabel, som er typen af den klasse, som createAnimator bør returnere. Underklasser ville blot implementere den variabel. Derefter ville createAnimator bruge den variabel til at returnere den korrekte type af Animator.

Svar #1

Ja, du kan gøre noget som dette (jeg har lavet et stort kodestykke, så jeg kan verificere, at alt kompilerer):

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;
}

Men det er ikke klart for mig, at dette er bedre end at implementere createAnimator i SubOuterClass1 og SubOuterClass2.

Svar #2

En anden tilgang er at bruge kraften i generics:

erklæring:

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

påkaldelse:

let animator = this.createAnimator(BridgeAnimator, animatorData);
await animator.animate();

Relaterede artikler