简体   繁体   中英

Subclasses as arguments to abstract method

In my program I have abstract classes Animal and Flower. Animals eat Flowers, but each Animal is only supposed to eat a certain type of flower (eg Rhinos only eat Roses, Turtles eat Tulips).

Right now my code basically looks like this:

public abstract class Animal {
    abstract boolean eatFlower(Flower f);
}

public class Rhino extends Animal {
    boolean eatFlower(Flower f) {
        if(!(f instanceof Rose)) return false;
        return (f.eaten = true);
    }
}

public class Turtle extends Animal {
    boolean eatFlower(Flower f) {
        if(!(f instanceof Tulip)) return false;
        return (f.eaten = true);
    }
}

I'm using instanceof to enforce this distinction where each Animal eats only one type of Flower. I'd like to do it by changing the method signatures of Turtle and Rhino to eatFlower(Tulip t) and eatFlower(Rose r) , respectively, but I don't think this is possible in Java.

Is there a better way than using instanceof to enforce this?

Yes, there is a better way. You probably don't want to enforce that type in the parameter.

Reason being, there is a major difference between run-time and compile-time polymorphism. Right now, you have to know (all) the types of flower an animal can eat when you compile the code. This prevents you from changing that data without a code update, and loses a fair amount of abstraction.

The contract of an Animal is that it eats some type of Flower , but you don't know what that is right away. In fact, only that particular animal knows exactly what type of flower it likes. Perhaps you have a Rhino with an intolerance to BlueRose s, who has to eat only RedRose s.

Unless you know without any doubt that all Rhino s will eat all Rose s and are willing to commit to that being the case forever (or until your next major version), you shouldn't make it a part of the interface's public contract. At some point in the near future, Rhino s may realize that Daisies are equally delicious, and a sub-species is born that can eat both. Once something is public, you can only make it less restrictive.

When a Rhino comes upon a Flower , it has to choose whether or not it is equipped to eat that particular flower. The rhino does not list all good flowers when it wakes up every morning, so you probably shouldn't hard-code them all in your code.

You have two options, depending on how you want to enforce that choice. You can check that the passed Flower is an instanceof a known-good type, which requires you to know all good types are compile-time. You could also add FlowerType getType() method to Flower s, call that, and compare the results with a Set<FlowerType> goodFlowers`. That allows you to change the acceptable flowers at runtime, if such a thing is necessary.

Speaking generally, multiple instanceof checks (which you'll get, if rhinos can ever eat another kind of flower) can often be the indicator of a design flaw. They can harm abstraction, by closely binding two otherwise-unrelated types, and may be replaced with data (from an enum, database, file, or other source).

You can use generics. Something like this:

public abstract class Animal<T extends Flower> {
    abstract void eatFlower(T f);
}

public class Rhino extends Animal<Rose> {
    void eatFlower(Rose f) {

    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM