Sunday, November 4, 2012

JavaFX and the Missing Interfaces

During the development of my company's JavaFX based application framework, I have often been challenged by the extensive use of superclasses rather than interfaces in JavaFX. Here are my thoughts on what could be done to improve JavaFX in this regard.

The Single Inheritance Problem

Being a feature rich API, it is no surprise that JavaFX is quite complex, containing more than 700 class files. More surprisingly, however, is the fact that the API contains very few interfaces – even key extension points like Node, Parent, Shape, and Control are all implemented as classes. This gives the JavaFX API developers more control over what is going on at runtime, since final methods can be placed in these classes, thereby enforcing specific runtime behavior. From a design perspective, however, it does not work well in Java's single inheritance world.
Problems arise when application developers and third party framework developers extend JavaFX classes to add more features. As an example, an essential feature of my company's framework is internationalization, and to enable it across all of the user interface, I have extended each of the built in JavaFX controls; my internationalizable text field is called ITextField, and it extends TextField. Similarly, IComboBox extends ComboBox, IButton extends Button – you get the picture.
Since all of these classes have a number of common methods, they all implement a common interface called IControl. When an application developer sees a class implementing IControl, he will immediately be aware that a number of additional features are available.
Some parts of my framework add cross cutting features, and some of those features work only for IControls; consequently I have API methods similar to
public Something fooBar(IControl control) {…}
Although this guarantees that the control parameter is internationalizable, there's a catch. Have you spotted it? Since the parameter is required to be an IControl there is no longer a compile time requirement that it is also a Control. Why? Well, since Control is a class – not an interface – there is no way I can make my IControl interface extend Control, to signal that only Controls should be IControls. So, if a developer has not yet realized this requirement, he could turn any class into an IControl by implementing the interface – it does not even have to be a scene node. Obviously, my framework will fail at runtime if the developer implements IControl from a non-control class; but there is no way to reveal the mistake at compile time.
As an additional nuisance, this problem also requires me to do a lot of casting internally in my framework code. Everytime I implement a method that takes an IControl parameter, I have to cast it to Control to invoke Control spefic methods like setTooltip() or Node specific methods like isVisible() or getBoundsInLocal().

A Solution

From the many constructive discussions I have had with Richard Bair, Jonathan Giles and other JavaFX API developers, it is crystal clear to me, that backwards compatibility is valued immensely – and with good reason. The upside is that new versions of JavaFX does not break any code – yours, or mine, or the UI of a critical application used at a hospital or a nuclear power plant. That is a good thing. However, the downside is, it is now very hard to fix the missing interfaces problem.
In an ideal world, I would like a dozen JavaFX classes to be rewritten as interfaces. The existing Control class would be renamed to ControlBase, allowing it to implement an interface called Control. Obviously this would immediately break the code of each and every JavaFX application on the planet, so that idea does not go on my Christmas wish list this year. However, we are not prevented from introducing an interface of a different name. So while we cannot get
NodeBase implements Node 
ParentBase implements Parent
ShapeBase implements Shape
ControlBase implements Control
we can get
Node implements SceneNode 
Parent implements SceneNodeParent 
Shape implements SceneShape
Control implements SceneControl
where each of the SceneX interfaces simply contain all of the public methods of their class counterpart.

Conclusion

Admittedly, this does not follow any de facto Java naming standard, but it does work – without breaking any existing code, these interfaces solve the design problem. I can now rewrite my IControl interface as
interface IControl extends Control {…}
thereby requiring all IControl's to be real Controls. If I have very finicky requirements for an input parameter, I can even specify new interfaces which combine requirements, like
interface Demanding extends Control, LooksLikeADream, SmellsLikeRoses
{…}
which means that a method requiring a Demanding parameter will get exactly what it needs.
I warmly welcome any constructive feedback on this suggestion, either here or on the openjfx-dev mailing list, before I post a feature request to the JavaFX team.