javascript - Typescript do not allow subclasses methods if variable is superclass type - Stack Overflow

I got some typescript interface, abstract class and implementing sub-classes: Animal classesabstract

I got some typescript interface, abstract class and implementing sub-classes:

// Animal classes

abstract class Animal {
    abstract sound(): string;
    constructor(public name: string) {
    }
    eat(food: string): string {
        return "I eat this now: " + food;
    }
}

class Snake extends Animal{
    constructor() {
        super("Snake");
    }
    sound() {
        return "Sssssss";
    }
}

class Owl extends Animal{
    constructor() {
        super("Owl"); 
    }
    sound() {
        return "Hu-huu";
    }

    // Owl can also fly!
    fly() {
        return "I can flyyyy";
    }
}

// Box classes

interface BoxInterface {
    animal: Animal;
}

class Box implements BoxInterface {

    animal: Animal;

    constructor(animal: Animal) {
        this.animal = animal;
    }

}

As you can see the idea is that we have a Box and some kind of Animal in the box - in our example it can be Snake or Owl.

Now we can create Box with Owl inside.

let box = new Box( new Owl() );

And now the problem - using any method declared in superclass is pletely fine:

box.animal.sound(); // this is fine

but as you can see Owl have additional function fly() and because fly is not declared in Animal it throw:

box.animal.fly(); // Property 'fly' does not exist on type 'Animal'.

Also the same happens when creating normal variable:

let animal:Animal;
animal = new Owl();
animal.fly();

As addition Animal class do not have to be abstract, it can be normal class or interface - result will be the same.

My question is: why typescript throw it if my class is superset of other class. I think the main idea of interfaces and typing is guaranteeing that object has some properties like eat() or sound() in this example.

Im very new in typescript so it can be that I missed something, anyway how I can achieve that some variable must be some type but allowing additional methods in subclasses?

I got some typescript interface, abstract class and implementing sub-classes:

// Animal classes

abstract class Animal {
    abstract sound(): string;
    constructor(public name: string) {
    }
    eat(food: string): string {
        return "I eat this now: " + food;
    }
}

class Snake extends Animal{
    constructor() {
        super("Snake");
    }
    sound() {
        return "Sssssss";
    }
}

class Owl extends Animal{
    constructor() {
        super("Owl"); 
    }
    sound() {
        return "Hu-huu";
    }

    // Owl can also fly!
    fly() {
        return "I can flyyyy";
    }
}

// Box classes

interface BoxInterface {
    animal: Animal;
}

class Box implements BoxInterface {

    animal: Animal;

    constructor(animal: Animal) {
        this.animal = animal;
    }

}

As you can see the idea is that we have a Box and some kind of Animal in the box - in our example it can be Snake or Owl.

Now we can create Box with Owl inside.

let box = new Box( new Owl() );

And now the problem - using any method declared in superclass is pletely fine:

box.animal.sound(); // this is fine

but as you can see Owl have additional function fly() and because fly is not declared in Animal it throw:

box.animal.fly(); // Property 'fly' does not exist on type 'Animal'.

Also the same happens when creating normal variable:

let animal:Animal;
animal = new Owl();
animal.fly();

As addition Animal class do not have to be abstract, it can be normal class or interface - result will be the same.

My question is: why typescript throw it if my class is superset of other class. I think the main idea of interfaces and typing is guaranteeing that object has some properties like eat() or sound() in this example.

Im very new in typescript so it can be that I missed something, anyway how I can achieve that some variable must be some type but allowing additional methods in subclasses?

Share Improve this question asked Dec 14, 2017 at 16:18 GrivaGriva 1,73821 silver badges38 bronze badges 0
Add a ment  | 

5 Answers 5

Reset to default 4

Because typescript will not perform inference type for animal: Animal;

As you defined animal as an Animal, so only methods and fields defined in Animal will be available.

It is the way which strong typing works.

If you declare animal as :

animal

or

animal : any

You will be able to invoke any method on it but you lose the type checking.

As workaround, you could use a cast to manipulate a Owl if the animal IS a Owl.

Type assertions are a way to tell the piler “trust me, I know what I’m doing.” A type assertion is like a type cast in other languages, but performs no special checking or restructuring of data. It has no runtime impact, and is used purely by the piler. TypeScript assumes that you, the programmer, have performed any special checks that you need.

if (box.animal instanceof Owl){
    (box.animal as Owl).fly
}

But a better way would be to have a generic Box :

class Box<T extends Animal> implements BoxInterface {

    animal: T

    constructor(animal: T) {
        this.animal = animal
    }

}

Now you can write :

let box = new Box<Owl>(new Owl());
box.animal.sound()
box.animal.fly()

In any case, as IMSoP very well said : you have to know at some point that what you have is an Owl if you want to apply method specific to Owl.

Because Boxes are only guaranteed to have Animals in them. Not all Animals can fly().

You could cast (type assert) Box's Animal to an Owl, and then have it fly:

(box.animal as Owl).fly()

You are right that a base class contract guarantees a minimum set of methods known to be available on all its sub-types.

However, TypeScript is enforcing an additional constraint here, that you should only invoke methods that you know are available on the object that you have.

In this case, all you know while you are writing the code box.animal.fly(); is that you have an Animal; therefore, you should only call methods that all animals have.

Consider what would happen if this check was not there:

let animal:Animal;
animal = new Snake();
animal.fly();

You would get some kind of error at runtime. The idea of the type checker is to spot this possibility for you, and make you write code which is "safe" in this sense.

This is a OOP matter. When you declare a variable of a certain type and assign a value to it the value (sort of) has 2 types: a runtime type and a static (pile time) type. The type considered by the piler is the one declared for that variable (which is animal, not Owl and so can't have a method fly()). This has implication on overriden methods (they could be resolved by static or dynamic information, depending on language specification). For more info look at

What is the difference between statically typed and dynamically typed languages? https://en.wikipedia/wiki/Multiple_dispatch

All other answers are valid. To your problem, you are best off using generics

class Box<T extends Animal> {
  constructor(public animal: T) { }
}

const box = new Box(new Owl())
box.animal.fly()

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1742326055a4422773.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信