Overriding Methods

A boss inherits describe from Monster, so it describes itself like any monster: Dragon (300 health). But a boss should also say which phase it's in. A derived class can write its own version of a method of its parent. That's called overriding the method:

class Monster {
    name: string;
    health: number;

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

    describe(): string {
        return `${this.name} (${this.health} health)`;
    }
}

class Boss extends Monster {
    phase: number = 1;

    override describe(): string {
        return `${super.describe()}, phase ${this.phase}`;
    }
}

const monsters: Monster[] = [new Monster("Goblin", 30), new Boss("Dragon", 300)];
for (const monster of monsters) {
    console.log(monster.describe());
}

This prints:

Goblin (30 health)
Dragon (300 health), phase 1

Three things are new here:

  • The method in Boss has the same name as the one in Monster, so for a boss, it replaces the inherited one. Which version runs depends on the object, not on the type of the variable: in the loop, monster is a Monster for TypeScript, but the second object is a boss, so its own describe runs.
  • super.describe() calls the parent's version. The boss doesn't have to repeat how monsters describe themselves: it takes that text and adds the phase. (Careful: this.describe() would call the boss's own method, which would call itself again and again, until the program stops with RangeError: Maximum call stack size exceeded.)
  • The keyword override says: “This method replaces a method of the parent.” It's optional, but it lets TypeScript check you.

Why override helps. Without it, a typo in the name doesn't replace anything; it just adds a new method:

class Monster {
    name: string;
    health: number;

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

    describe(): string {
        return `${this.name} (${this.health} health)`;
    }
}

class Boss extends Monster {
    phase: number = 1;

    descibe(): string {
        return `${this.name} (${this.health} health), phase ${this.phase}`;
    }
}

const monsters: Monster[] = [new Monster("Goblin", 30), new Boss("Dragon", 300)];
for (const monster of monsters) {
    console.log(monster.describe());
}

This prints:

Goblin (30 health)
Dragon (300 health)

The boss's method is never called, and nothing tells you why. Write override descibe(): string, and TypeScript finds the mistake:

main.ts(18,14): error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Monster'. Did you mean 'describe'?

An overriding method must also fit the parent's: it must work with the same arguments and return a value of the type that the parent's method returns. If the boss's describe returned a number, TypeScript would report Property 'describe' in type 'Boss' is not assignable to the same property in base type 'Monster'. (TS2416). That's why a boss can stand in for a monster anywhere: everything you can do with a monster works with a boss too.

Instructions

The dragon should describe itself with its fire power, but the program prints just Red Dragon (280 health). Write override in front of the dragon's method. TypeScript then finds the mistake; fix it, so that the program prints:

Goblin (10 health)
Red Dragon (280 health), fire 40

The dragon's method repeats how monsters describe themselves. Use super.describe() for that part instead. The program prints the same.

Dragons have thick scales: they take only half the damage. Override takeDamage in Dragon (with override), so that it calls the parent's version with half the amount. The program prints:

Goblin (10 health)
Red Dragon (290 health), fire 40

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

8/9