A common mistake that newcomers to object-oriented programming make is to try to fit every property of complex objects into one inheritance hierarchy. Inheritance is the most tightly coupled form of making a class X depend on a class Y. If X extends Y, then every change to Y must necessarily affect X, and all of X's methods must be careful not to violate Y's invariants (see Liskov substitution principle, for instance).
Instead of shoving everything into one hierarchy, think about how your objects may have properties that can be modelled more neatly. You're right to be hesitant to use a bunch of Booleans, but that's not what I'm suggesting. The flying/non-flying problem is orthogonal to the hostile/passive problem, so they need different solutions.
For the former, you might consider giving every creature type a Movement
field. Perhaps something like
interface MovementAbility {
...
}
class FlyingAbility implements MovementAbility {
...
}
class WalkingAbility implements MovementAbility {
...
}
Then every creature gets one of these
class Creature {
private MovementAbility movementAbility;
...
}
or multiple, if there are creatures who can walk and fly and who need to be able to decide which one to do.
class Creature {
private List<MovementAbility> movementAbilities;
...
}
Then you can do the same thing for hostility. Something like
interface Disposition {
...
}
class HostileDisposition extends Disposition {
...
}
class PassiveDisposition extends Disposition {
...
}
Then your Creature
could look like
public class Creature {
private MovementAbility movementAbility;
private Disposition disposition;
}
and the creature can call methods on the Disposition
object to determine how to behave in the presence of a player, then on the MovementAbility
object to determine how to get from A to B.
Exactly what methods go in each of those classes is dependent on your needs for your particular project, but the key thing to aim for is not doing instanceof
checks. It's really easy to write code in Creature
that sits there and says if (movementAbility instanceof FlyingAbility) { ... }
, but that's no better than just using a Boolean in the first place. Instead, when you need to do something that depends on how a creature moves, make it an abstract part of MovementAbility
and call that function virtually.