As already pointed out in the comments and answers: It all depends on what you are trying to model there.
But first of all, regarding the intention that you mentioned:
So I'm only creating them in case I ever need to use an instanceof conditional statement.
Be careful with that. You should avoid modeling the different behavior of objects depending on their type. Here, it does not matter whether the type information is
- implicitly available via
instanceof
checks
- explicitly via a method like
boolean isMotorCycle()
, as suggested in a comment
- explicitly via something like an
enum Type { ... }
and a switch
over the type.
They all suffer from the same problem: When you add a new class/type to your hierarchy, then you have to adopt and update all places where this type information was queried. This may become a maintainance nightmare.
There are legitimate uses for type checks. But when you end up with writing code that frequently does checks like this
void recharge(Vehicle v) {
if (v instanceof ElectricCar) ((ElectricCar)v).attachPlug();
if (v instanceof GasolineCar) ((GasolineCar)v).fillFuel();
if (v instanceof Bike) ((Bike)v).getDriver().takeRest();
...
}
then this is a strong indication that something is wrong with your hierarchy. It might be that the base class, Vehicle
, is simply not powerful enough. In the example above, one could then consider pulling the recharge()
method into the Vehicle
class, and simply call it, relying on the polymorphic implementation.
It might, in the worst case, also be that the concepts that you are trying to model are simply too unrelated to be combined in a single hierarchy.
You mentioned that you want to do collision detection for these objects. This sounds like you're already approaching a solution like this:
class Bike extends Vehicle {
@Override
void collideWith(Vehicle other) {
if (other instanceof Car) collideWithCar((Car)other);
if (other instanceof Segway) collideWithSegway((Segway)other);
...
}
}
There are more elegant solutions for that. You might want to read about the problem related to Double Dispatch, and maybe have a look at the Strategy Pattern.
More generally, concerning the question of whether you have "too many classes", there are some general rules fo thumb.
One of them is the Interface Segration Principle, stating that you should create serval client-specific insterfaces, instead of one large interface that does everything and therefore combines otherwise unrelated methods. So you don't have "too many classes", as long as each of them serves a purpose. You could ask yourself:
- Who will have to differentiate between a
GasolineCar
and an ElectricCar
?
- Who will have to deal with only one of these interfaces?
- When they both extend the same base class: Who will be able to use the base class (or will everybody have to know the specific implementation anyhow?)