Are interfaces a bad design pattern?
Ok, there is no need for mysteries here: yes, I do think that interfaces are a bad design pattern. But to state something like this, I am aware that I need some solid proofs. Please read my notes on OOP for a proper introduction.
The main idea behind interfaces
The first concrete formalisation of OOP appears with Alan Kay’s work and SmallTalk’s implmentation. One of the main properties of OOP are the ability to work with objects and create inheritance between these objects. Inheritance is a simple mecanism allowing to base an object on another. The inhrited class maintains the same behaviour and the same properties than the base class without having to declare any variable or method. SmallTalk only provides a single inheritance mecanism at the time of its creation. C++ is the first language to provide multiple inheritance. But, with multiple inheritance comes the diamond problem. The problem itself is simple: when a class extends two other classes providing the same method with the same signature, there’s no way to know which implemention is going to preveil. Java’s solution to the diamond problem is radical: suppress multiple inheritance.
Though, there are some very concrete use cases of multiple inheritance. The most common one can be found in graphical interfaces: every object representing something to be displayed must manipulated as a displayable object thus, it has to be possible to insert it in a diaplayable objects list. But, on the other hand, not every displayable object has the same nature. Buttons must be manipulated as clickable object and labels, not. So, how make multiple inheritance be possible without allowing multiple inheritance? Java’s answer is the interface.
Interfaces are somehow very similar to C’s header files. You can only declare methods signature, no variable and no implementation unless they are static. This way, the diamond problem is solved: the implementing object can be of multiple types but can only have one implementation. As any object can inherit from one, and only one class, the preveiling implementation is either its own one, or its parent’s, or its grand-parent’s, and so on, recursively until the primary parent. There cannot be any ambiguity. It is then the developer’s work to ensure that an implementing object provides an implementation for every method of the interface.
But here appears the first limitation: interfaces cannot have too many method signatures. An interface
that have many method signatures constrain the developer to provide an implementation for methods
that are not guaranted to be useful. As a result, interfaces are often only partially implemented.
The implementation of the methods for which the developer has no interest often consist of throwing
a NotImplementedException
. Sometimes don’t even do anything.
This is rather a bizarre way of solving the problem. But it does not stops here…
The abstract classes
In some use cases, the developer must be forced to provide an implementation because the use case is too specific to provide a generic implementation. In particular, it happens inside the algorithm libraries. For instance, the sort algorithms are not able to sort anything unless you tell them how two objects compare to each other. The solution then, is to let the developer declare an object as comparable to another. A default implementation cannot be provided because the algorithm has no idea of what’s inside the collection. Actually, the algorithm only need to know if this object, compared to that object, is before or after. But, while a default implementation cannot be provided for this comparison method, a default implmentation can be provided for the other methods of the class.
This is rather useful to provide utility methods. Let’s take the example of Android’s
Cursor that provide an access
to a database result query. This class provides the methods getFloat(int columnIndex)
,
getString(int columnIndex)
, getInt(int columnIndex)
, etc. that take the DB’s column index as
parameter. But what you want is to retreive the result by the column’s name. So you have to write
the following:
Now, let’s say you want to provide a direct getFloat(String columnName)
,
getString(String columnName)
, getInt(String columnName)
, etc. to your object. The solution is
the abstract class:
You subclass only has to implement the getCursor()
method to have a direct access to the
getFloat(String columnName)
, getString(String columnName)
, getInt(String columnName)
methods.
An abstract class is a standard class that cannot be instanciated and can declare method signatures without implementation. Abstract classes are as old as C++ and also exist in Java. So Java provides three levels of type: standard classes, abstract classes and interfaces.
Java 8 and default methods
Java’s standard library provides a lot of interfaces. A ridiculous amount of interfaces. At some point, when learning Java, I noticed that most of my time reading Java’s documentation was spent trying to find an implementation of some interface because 3 methods out of 4 in Java takes an interface as parameter. Just opening a stupid file or convert a date is a pain in the ass.
The funny thing is that AWT itself taunts Java. Event-driven programming is a torture for
that precise reason. The amount a code required to create an observer is just ridiculous. AWT
provides interfaces like MouseListener
to implement callbacks to a mouse event but, in the same library, just next to the interfaces,
AWT provides implementation classes like
MouseAdatater
with empty methods.
Why write an interface to immediately provide an implementation!? Why not just create the class? Oh, yeah… Java can’t support multiple inheritance, that’s right… Or maybe…?
Because Java 8 brought a new feature: default methods. The idea is to let an interface provide a default implementation if the implementing class does not provide its own. To my great astonishment, everybody praised this as a great step forward. And I was like: “Wait, dudes… That’s multiple inheritance…”.
Let’s summerize: an interface is some kind of class that cannot be instanciated and cannot provide an implementation to its methods… unless you choose to provide an implementation to its methods.
An interface in Java 8 is then… an abstract class!
C++’s solution
The worst part is that Java proposed, with its interface thing, a solution to a problem that was already solved by C++. The diamond problem in C++ is called an ambiguity. If the compiler can”t determine which implementation to use, it throws an exception and forces the developer to provide an explicit implementation. This elegant solution could have been used in Java using the new default keyword. For instance, doing something like this:
Python also features multiple inheritance. Though it uses an algorithm to determine which implementation to choose: the method resolution order. Scala, which is compatible with the JVM, works on the same principle. This shows that the limitation is a conscious choice from the developers of Java.
And I really don’t buy the safety argument. Nobody will convince me that Python, C++ or Scala programms have statistically more bugs than Java’s. Suffice for the developer to be aware of what she or he is doing. And, on a more personnal note, I don’t understand the idea of a language that chooses what a developer can or cannot do. Depriving oneself of valuable features that could elegantly solve some exotic problems just because someone, somewhere, someday could possibly misuse it, is not a smart agument to me. It is riduculous to fund theorical computer science researches, to train computer science students for years and teach them algorithmics just to let them use languages with training wheels in the end.
We must face the facts: Java is a bad language, built on wrong principles, with bad ideas that went wrong. Indeed, some alternatives exist and I mentionned some of them. But, unhopefully, Java continues to be dominant in industry because nobody, in engineer schools teach something else…