javascripttypescriptclasspolymorphismsubclass

How to call a child method after casting an instance from parent to child class?


If I have a class B that inherits from class A and overrides one of its methods, and I have, in TypeScript:

const a: A = new A();
(a as B).toto();

Why is the toto() method that will be called the one implemented in the parent class (i.e. A)?

In TypeScript, I have a class called Drug that has a name and an effiency, and a method updateEfficiency() that decreases efficiency by 1:

class Drug {
  name: string;
  efficiency: number;

  constructor(name: string, efficiency: number) {
    this.name = name;
    this.efficiency = efficiency;
  }

  updateEfficiency() {
    this.efficiency -= 1;
  }

}

I have another class called BottleOfWine that inherits from Drug class and overrides the updateEfficiency() method:

class BottleOfWine extends Drug {

  constructor(efficiency: number) {
    super("Old bottle of wine", efficiency);
  }

  updateEfficiency() {
    this.efficiency -= 2;
  }

}

I have a third class called Inventory which contains an array of Drug, and a method updateAll()that updates the efficiency of all the drugs:

class Inventory {

  drugs: Drug[];

  constructor(drugs: Drug[]) {
    this.drugs = drugs;
  }

  updateAll() {
    this.drugs.forEach((drug) => {
      switch (drug.name) {
        case "Old bottle of wine":
          // here I expect to call the child method that decreases the efficiency by 2,
          // but the parent class is called instead
          (drug as OldBottleOfWine).updateEfficiency();
          break;
        case "Insulin vial":
          // nothing to do
          break;
      }
    });
  }

}

On the updateAll() method, I want, depending on the name of the drug, to cast the drug object into a more specific object (child) and call the method of the child class.

Example:

const inventory = new Inventory([new Drug("Old bottle of wine", 10)])
inventory.updateAll();
console.log(inventory.drugs[0].efficiency); // I expected 8 (ie 10 - 2), but got 9 (ie 10 - 1)

How could you explain it?


Solution

  • Targeting my 2nd comment ...

    ... Regarding the OP's use case ... new Drug("Old bottle of wine", 10) does create a Drug but not a BottleOfWine instance. Thus, the result seen by the OP, an efficiency value of 9 is exactly how it should be. The question is, why does the OP want to distinguish drug types by an arbitrary name? A drug item's name is nothing one can rely on. The drug item's type (determined by the class constructor function) is the only reliable criteria. The OP needs another approach for letting people create the correct drug type instances or a better explanation of the entire matter.

    One possible solution of controlling, how drug instances are created is to not use sub-classing at all. In case of a sole Drug class implementation nothing can be confused. In addition one would change the implementation to an additionally to be provided parameter which reflects the in/decrease of a drug item's efficiency value ...

    const inventory = new Inventory([
      new Drug("Old bottle of wine", 10, -2),
      new Drug("Homegrown Weed", 20, -1),
    ]);
    
    console.log({
      drugList: structuredClone(inventory.drugs),
    });
    inventory.updateAll();
    
    console.log({
      drugList: structuredClone(inventory.drugs),
    });
    .as-console-wrapper { min-height: 100%!important; top: 0; }
    <script>
    class Drug {
    
      constructor(name = '', efficiency = 0, efficiencyDelta = 0) {
    
        name = String(name);
        efficiency = Number(efficiency);
        efficiencyDelta = Number(efficiencyDelta);
    
        Object.assign(this, { name, efficiency, efficiencyDelta });
      }
      updateEfficiency() {
    
        this.efficiency += this.efficiencyDelta;
      }
    }
    
    class Inventory {
    
      constructor(drugs = []) {
        this.drugs = Array.from(drugs);
      }
      updateAll() {
        this.drugs.forEach(drug => drug.updateEfficiency())
      }
    }
    </script>