Introduction
We often hear the advice that we should prefer composition over inheritance. Then naturally come the questions: Are there valid cases at all to use inheritance? Is it possible that inheritance, one of the fundamental OOP principles, is actually an anti-pattern and unnecessary language feature?By inheritance here I mean only class inheritance and not interface extension.
Goodbye Class Inheritance
The most common reason for class inheritance found in practice is code reuse. When people realize they need some common code in two or more classes, they pull it up in a common base class (usually an abstract one). But what would you do if you need to reuse code from several other classes? There is no multiple inheritance in Java. The right way to reuse some common code is via composition, i.e. extract the common code in a separate class and use it via a reference.The next reason for inheritance is method overriding, i.e. template method pattern. When people realize they need some variability in behavior, they define a method for it (often abstract) and implement it in different ways in different subclasses. This is also wrong for several reasons. You cannot change the variable method implementation dynamically without changing the whole object. Also if you have several axes of variability, i.e. several abstract methods, how many subclasses do you need to cover all possible combinations? Again the right way to do that is via composition. Each independently variable part is extracted in a separate interface. Different implementations of these interfaces are injected from outisde, i.e. strategy pattern.
Can you suggest cases where it is still best to use class inheritance? Post a comment.
Generally, inheritance violates other OOP principles like encapsulation as it creates tight coupling between involved classes.
The conclusion is that the availability of this feature (implementation inheritance) leads people into wrong designs. So the Java language can be both improved and simplified if classes are not allowed to extend other classes. Still an interface can extend other interfaces and a class can implement a number of interfaces.
Removing this feature can simplify the language significantly. Additional language features can also be removed as they are no longer necessary.
- abstract keyword is no longer necessary and can be removed. Abstract coding can get you only abstract money. :)
- protected keyword is no longer necessary and can be removed
- final keyword on classes and methods is no longer necessary
- (Probably this list can be extended further. Drop your suggestions in the comments.)
"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away."
Welcome Auto-binding
Still when your class implements an interface you often do not write the code from scratch but want to reuse some existing implementation. As we saw already the proper way to do that is via composition. Something like this
class A implements I
{
private I anI;
...
public void foo() { anI.foo(); }
public void foo() { anI.foo(); }
public void moo() { anI.moo(); }
...
}Unfortunately we have no way to tell that anI provides the implementation of interface I, so we have to implement all the methods of I in A just by delegating to anI. Boring boilerplate! This is where the second proposal for the language improvement kicks in. You can call it auto-wiring or auto-binding but it does just that, specify that an interface is implemented by a specific class member. Something like this:
class A implements I via anI
{
private I anI;
...
...
}
The compiler can automatically generate the necessary delegation methods or an optimized implementation could even skip this overhead.
If a class needs to customize the interface implementation it can still implement some of the interface methods, thus overriding these methods from the external implementation.
Instead of a field also a method could be specified in case the interface implementation should be calculated dynamically. Something like this:
class A implements I via getI
{
private I getI() { ... }
}
}
I find these two proposals for Java language improvement nicely complement each other and naturally lead developers in the right direction.
Until these improvements are implemented in Java (version 15 probably :) you can try to avoid class inheritance in your code.
I agree that composition should be preferred over inheritance in most cases. Yet, inheritance offers a much more natural way to express an "is a" relationship between classes than composition (even thought it's possible to use composition as well). This relationship appears relatively often, which is why inheritance is also used relatively often, in a perfectly legal way.
ReplyDeleteInheritance can also be abused, which could lead to some pretty ugly and hard to understand code. But this is not a valid enough reason to remove a useful feature from a language, from my perspective.
Inheritance is present in most JVM-based programming languages, including new and extremely innovative ones. When it comes to programming languages, I tend to value "expressive power" much higher than "simplicity". Long live Scala ;-)
I prefer composition over inheritance too.
ReplyDeleteBut I would like to comment your second proposal (about the 'via' operator). To be honest, I don't see any real applications of this construct. Maybe anI is instance of a class providing default implementations of methods in the I interface?
In languages like Scala there are no such things as interfaces, they are rather replaced by traits. These look in a way like Java interfaces. But you can have there method implementations (and not only declarations). Something similar we are going to have in Java 8 as well: the default method implementations in interfaces. So you will be able to provide default implementations of foo() and moo() in your I interface and then you decide whether you want to override those implementations in class A.