Paper_Folding
Paper_Folding

Reputation: 69

How to declare a variable's type as class that inherits/extends from a base class in typescript?

I must apologize if this question is duplicated, but I googled first without getting a useful answer.

Typescript is quite new to me, today I have met a problem of it.

Scenario

In typescript, I have a base class:

abstract class Animal {
    // Animal class definition here
    // ...
}

And I have several sub-classes that inherits from Animal class:

class Cat extends Animal {
    // Cat class definition here
    jump() { console.log('Cat jumps high like a cat.') }
    // ...
}
class Dog extends Animal {
    // Dog class definition here
    swim() { console.log('Dog swims like a dog.') }
    // ...
}

Question

How can I declare a variable whose type is the sub-class of Animal in typescript?
like this, but it is not working code:

let animal: any extends Animal = new Cat();
animal.jump(); // typescript will warn me: "Property 'jump' does not exist on type 'Animal'"

or like this in java, which is java's working code:

// java code
Animal animal = new Cat();
animal.jump();

Trivia

let animal: Cat | Dog = new Cat(); is not a good answer, because I may implement more sub-classes that extends from Animal class in the future.
Also, I did not merely want to declare a variable like I did above (in that case, I can just use let animal = new Cat();), in fact, this is the actual scenario I have encountered:

class House {
    private animalThatStaysIn; // what type should I declare for this member? I think it is the class that inherits/extends from Animal
    constructor(animal) {
        this.animalThatStaysIn = animal;
    }
}
let cat = new Cat();
let catHouse = new House(cat);
let dog = new Dog();
let dogHouse = new House(dog);

What's more, I cannot change the definition of Cat or Dog class for they are from my external import(node_modules folder).

Upvotes: 2

Views: 1687

Answers (2)

Robby Cornelissen
Robby Cornelissen

Reputation: 97152

These are your options:

1. Add additional types in the type hierarchy

abstract class Animal {}

abstract class JumpingAnimal {
  abstract jump(): void;
}

abstract class SwimmingAnimal {
  abstract swim(): void;
}

class Cat extends JumpingAnimal {
  jump() { console.log('Jumping like a cat'); }
}

class Fish extends SwimmingAnimal {
  swim() { console.log('Swimming like a fish'); }
}

const cat: JumpingAnimal = new Cat();
cat.jump();

2. Use interfaces to define common behavior

abstract class Animal {}

interface Jumper {
  jump: () => void;
}

interface Swimmer {
  swim: () => void;
}

class Cat extends Animal implements Jumper {
  jump() { console.log('Jumping like a cat'); }
}

class Fish extends Animal implements Swimmer {
  swim() { console.log('Swimming like a fish'); }
}

const cat: Animal & Jumper = new Cat();
cat.jump();

This has the additional benefit that you can use intersection types to specify multiple interfaces:

class Dog extends Animal implements Jumper, Swimmer {
  jump() { console.log('Jumping like a cat'); }
  swim() { console.log('Swimming like a fish'); }
}

const dog: Animal & Jumper & Swimmer = new Dog();
dog.jump();
dog.swim();

3. Use union types to create type categories

type JumpingAnimal = Dog | Cat;

const cat: JumpingAnimal = new Cat();
cat.jump();

4. Use type guards to narrow the type

const cat: Animal = new Cat();

if (cat instanceof Cat) {
  cat.jump(); // no error
}

5. Use type assertions to tell the compiler: "Trust me, I know what I'm doing"

const cat: Animal = new Cat();
(cat as Dog).swim(); // runtime error

Or some combination of the above.

Upvotes: 2

slebetman
slebetman

Reputation: 113906

You have a design problem. You are missing an intermediate type: HouseAnimal (or Pet). Your actual design should be something like this:

abstract class Animal {
    // Animal class definition here
    // ...
}

abstract class HouseAnimal extends Animal {
    // Features only house animals have
}

class Cat extends HouseAnimal {}

class Dog extends HouseAnimal {}

class House {
    private animals: HouseAnimal
}

This is probably the most correct approach using inheritance. Alternatively you could also make house animals have certain features that non-house-animals have if you don't want an intermediate type. To enforce this in the type system you can use an interface:

abstract class Animal {
    // Animal class definition here
    // ...
}

interface HouseAnimal {
    // These animals can be in houses
}

class Cat extends Animal implements HouseAnimal {}

class Dog extends Animal implements HouseAnimal {}

class House {
    private animals: HouseAnimal
}

Upvotes: 0

Related Questions