I'm having trouble understanding when/why to implement inheritance and when/why to implement inheritance via interface. Please bear with me as I explain..
Let's say we have a parent class Animal
and we wish to extend it with 3 subclasses: Dog
, Cat
and Mouse
.
Suppose all Animals are able to eat()
, sleep()
, scratch()
and move()
. and Dog
is able to pant()
. With this knowledge we would proceed to add the first 4 behaviors to the Animal
superclass and have Dog
, Cat
and Mouse
extend Animal
. We would also add the method pant()
to the Dog
class bec only dogs pant()
.
Now what happens if we wish to add another method called waggleTail()
but only Cat
and Dog
exhibit this behavior. We cannot add this behavior to Animal
bec then Mouse
will also inherit the behavior (and Mouse doesn't waggle it's tail). An alternative is to add the method waggleTail()
to both the Dog
and the Cat
classes but not to the Mouse
class. This approach, however, does not make sense bec we would then violate the DRY principle (don't repeat yourself) by writing the method waggleTail()
twice. We want to write each method once, and only once.
Perhaps we could solve this problem by creating a new sub class that inherits from Animal
called TailWagglingAnimal
, add the method waggleTail()
to this subclass and then have both Dog
and Cat
inherit from this new subclass. This sounds reasonable until you realize that there are dozens of other such anomalies and we'd have to repeat this process again and again for each one (this would expand the inheritance hierarchy to no end).
Further, what if we have a specific kind of Dog
(let's call him the "Coton de Tulear") that exhibits all of the other behaviors of Dog
(such as panting) except that it doesn't waggle its tail. If we have "Coton de Tulear" inherit directly from Animal
it would be unable to pant(). If we had it inherit from Dog
it would be able to waggle its tail (bec Dog
extends TailWagglingAnimal
). If we had Dog
extend Animal
directly and then create a new subclass called TailWagglingDog
(as appose to TailWagglingAnimal
) then Cat will be unable to inherit this behavior (so we'd need to duplicate the behavior somewhere within the Cat hierarchy which violates the DRY principle).
What do we do?
Based on dozens of threads on stackoverflow (and several OO design books) it has been suggested to remove the method waggleTail()
from the Dog class and add it to and interface. Let's call the interface TailWaggler
and then have all dogs (except for "Coton de Tulear") implement this interface. However, I have trouble understanding why/how this is useful.
If you think about it, this means that all 50+ breads of dogs (let's assume there are 50 breads of Dog that need to exhibit this behavior) need to add the implements TailWaggler
keyword just bec a single kind of Dog
does not exhibit this behavior. Not only does this mean a lot of extra manual work on the part of the programmer (adding implements TailWaggler
to the beginning of each class) it means that all descendants need to be concerned with all of little and petty details of the behavior they exhibit (this would not be the case had we added this behavior to the parent class and extended the parent class). This may be fine if we only had a few such cases but what if we had dozens or hundreds of such cases? Finally, as we add new types of subclasses of type dogs there will eventually be one kind of Dog
are another that will not exhibit one of the behaviors of Dog parent class - so this means slowly but surely we'll need to remove almost all behavior from the (parent) Dog
class and add them to an interface? We'd then need to make sure all of the sub classes implement dozens of different interfaces. One might suggest that we group all related behavior in a single interface but this is only possible if the behavior exhibited by different dogs is uniform - what if this is not the case?)
Thanks!
We'd then need to make sure all of the sub classes implement dozens of different interfaces
If your class needs implementing too many interfaces check that it does not violate the Single Responsibility principle. Consider breaking the class into smaller ones.
Implementing several small interfaces instead of a large one conforms to Interface Segregation principle which leads to some positive consequences.
it means that all descendants need to be concerned with all of little and petty details of the behavior they exhibit
This is more about implementation difficulties. Multiple inheritance or auto delegation could help here. Since we don't have either in Java we have to choose between other options:
Implement delegation manually for each class :(
Use Java 8 interfaces if implementation is not complicated.
Use code generation library to autogenerate delegation code (e.g. look at lombok library @Delegate feature https://projectlombok.org/features/experimental/Delegate.html)
Inheritance is used when you want to morph a class which is of the same type of your parent class and which have a similar behavior.
Interface is used to declare a functionality of your class.
For example, Dogs, Cats and Mice are all Animals (same type) and they have similar behavior (they get born, grow up, die, move, eat, ...). So your Dog class can extend Animal.
Now, your interfaces declare their functions. Like we just seen, an animal can move and eat, so your Animal class can implement the interfaces Mover and Eater for example. Automatically, Dog, Cat and Mouse will inherit these interfaces, but a mouse will eat cheese while dogs and cats will eat meat. This is where you can @Override
(understand morph) the implementation behavior to declare what each class can eat.
If another animal can climb (a monkey), you will implement the Climber interface directly on the Monkey class. Making it slightly more evolved than a standard Animal.
For your tailwagging issue, you have to implement the Tailwagger interface in Dog and Cat, not in Animal (all animals are not tailwaggers). Of course you do not want to repeat the code, so you will also create another Class called StandardTailwag and use it as a field in Dog and Cat (composition). You then have to redirect the implementation to this class' methods, but this is the way to go if you want your code easier to maintain in the future.
You could also morph the StandardTailwag to DogTailwag and CatTailwag, and easily implement them with the same Tailwagger interface
Note that you can write default code and methods in Java 8's interfaces, but it is not recommended.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With