Javascript Interfaces: Putting The Java back into Javascript


Object-oriented is mostly about encapsulation, but it’s also about interfaces and inheritance. However, years of experience have caused most programmers to realize that class inheritance is mostly a bad idea; it breaks encapsulation while increasing cognitive load by introducing cryptic indirection. This is why you’ll often hear the sage advice to prefer composition over inheritance.

While Javascripters have gone to great lengths to figure out how to do OO & inheritance in Javascript, they almost universally refuse to use interfaces for their polymorphic needs. It’s like we’ve chosen to throw the best bits of OO away and keep whatever’s left!

Java & .NET developers on the other hand use interfaces everywhere. That’s partly due to the fact that they have no choice, but mostly it’s because these being statically typed languages means there are huge benefits to using interfaces in terms of compiler and IDE support.

In Javascript our compilers can’t recognize these kinds of mistakes, and our IDEs don’t currently provide any benefits either. And so, naturally, Javascripters often prefer lighter weight techniques like ad-hoc objects and call-back functions, rather than classes and call-back interfaces. However, compilation errors and refactoring support aren’t the only areas where formal interface definitions are helpful.

Why Javascript Interfaces

I could try to convince all Javascript developers to use interfaces because they are intuitively The right thing to do™, but that would be too hard. In fact, the sweet spot for Javascript interface use is within Javascript APIs that will be used by other developers. There are a number of reasons for this, mostly around API documentation and library upgradability:

  1. The interface provides the structure to hang quality API documentation on when using tools like jsdoc-toolkit — otherwise you have to document your informal interfaces within the methods where they appear.
  2. If there’s a call-back interface and additional optional methods have been added, these are automatically inherited when the API is upgraded, and everything just continues to work.
  3. If new methods have been added to an interface, but a developer’s implementation of that interface has not yet been updated then we can fail-fast with a clear indication of the problem when they upgrade the API.

Let’s use a fictional example to demonstrate some of these problems. Suppose we have a vending machine that serves chocolate:

/**
 * @constructor
 * This is the chocolate vending machine. Enjoy!
 */
Vendor = function() {
};

/**
 * Vends chocolate provided you insert enough money.
 *
 * @param {int} nCoin The amount of money being inserted.
 * @param {Function} fOnChocAvailable Call-back invoked if the operation was successful, and informing you how much change you now have.
 * @param {Function} fOnError Call-back invoked if there was a problem vending the chocolate, including failure information.
 */
Vendor.prototype.getChoc = function(nCoin, fOnChocAvailable, fOnError) {
    // do something
};

Although we have a formal definition of the Vendor class (as formal as Javascript can manage anyway), we are using two call-back functions rather than a more formally defined callback interface. One immediate side effect of this decision is that it has now become difficult to document the arguments that are passed to the fOnChocAvailable and fOnError functions.

Things get even worse further down the line when we encounter a use-case where we’d like to update the UI the instant the coin is registered by the vending machine, and before the actual chocolate is dispensed. We could add a third call-back, but three call-backs seems a little unwieldy. Worse still, we can only insert this new argument after all the others if we want to maintain backwards compatibility, even though it would feel more natural as the second argument.

Also, since code written against the initial API won’t pass this new call-back argument, the Vendor class will need to be careful to ensure that it’s been passed before making use of it. Now imagine if there were multiple implementations of Vendor (some written by users of the API), we’d be frozen out from making this change at all since we wouldn’t have access to the code to ensure that these other implementation also added this check code … yikes!

If we’d used a callback interface however, none of these problems would have occurred, and the Vendor class would instead look like this:

/**
 * @constructor
 * This is the chocolate vending machine. Enjoy!
 */
Vendor = function() {
};

/**
 * Vends chocolate provided you insert enough money.
 *
 * @param {int} nCoin The amount of money being inserted.
 * @param {VendorListener} oVendorListener Listener for all call-back information from the vending machine.
 */
Vendor.prototype.getChoc = function(nCoin, oVendorListener) {
    // do something
};

where there is now also a formally defined interface too:

/**
 * @constructor
 * @interface
 * This call-back inteface provides information about the vending machine.
 */
VendorListener = function() {};

/**
 * Call-back invoked once the coin has been accepted as valid, but before any chocolate has been dispensed.
 */
VendorListener.prototype.onCoinAccepted = function() {};

/**
 * Call-back invoked once the chocolate is available.
 * 
 * @param {int} nChangeReturned The amount of change returned by the vending machine.
 */
VendorListener.prototype.onChocAvailable = function(nChangeReturned) {};

/**
 * Call-back invoked if there is a problem.
 * 
 * @param {String} sProblemDescription A textual description of the problem.
 */
VendorListener.prototype.onError = function(sProblemDescription) {};

Notice how we can more easily communicate the arguments the call-backs provides to the developer. One could easily imagine a further improvement where the generic onError() method is replaced by the more specific call-backs:

  • onCoinRefused()
  • onInsufficientMoney()
  • onMachineEmpty()
  • onMechanicalError()

Try doing that with individual call-back functions!

So, the difference between a call-back function and an interface with one method is that one buys you the ability to grow your API in the future and one doesn’t. If you’re willing to sacrifice backwards compatibility then you don’t need to worry about this kind of stuff, and in fact very few javascript libraries do!

We write code like this here at Caplin, and sometimes when Javascript devs first see it they complain that it’s too much like Java. Well, maybe, but that’s not what motivates us to write this way. Ultimately, there are lots of benefits to doing API programming like this, so we just get used to having to justify our reasons every so often. If people wax on about how first class functions are just neater, more elegant, or cooler or something, it’s good to remember that one of the major reasons functional programming languages are of so much interest is that they allow side-effect free programming — something you don’t get with Javascript functions anyway!

What the future holds

Being a dynamic language, Javascript IDEs are never going to be able to offer reliable re-factoring tools in the same way as can be done for statically typed languages like Java. When it comes to content assist however, formal definitions of both the classes and interfaces available within a code-base is enough to allow very accurate content assist to be provided. We know this because we performed some experimentation a while back to find out what gaps remained before Eclipse could provide useful feedback for a large scale Javascript code-base using jsdoc annotations to provide additional clues to the IDE. Wanna guess what the one remaining missing feature was stopping everything from being perfect:

Support for @interface and @implements as psuedonyms for @class and @extends.

Somebody had already raised a bug for this in jsdoc-toolkit and I myself raised a bug for this in Eclipse, but even the Eclipse cats don’t seem to grok why this is such a good idea — such is the resistance to the use of formal interface definitions within Javascript.

If, like us, you think that really smart content assist for Javascript would be a good thing, then perhaps you’d like to help by commenting or voting up the Eclipse and jsdoc-toolkit issues for this:

In the meantime, long live interfaces within Javascript!

Related Posts with Thumbnails

5 Comments

  • Try Google Closure Compiler and WebStorm (IntelliJ IDEA) from JetBrains.

    They are the perfect combination for perfect OO JavaScript development.

    You can even document arguments of callback functions right in {Function} :)

    Google “Annotating JavaScript for the Closure Compiler”, Google has a nice superset of JSDoc.

    • Dominic Chambers says:

      Ooh, that’s really nice to see! Thanks so much for the heads up. Looks like I’ll be re-evaluating Intellij IDEA quite soon, and it’s also great to see that the Google guys get Javascript interfaces too!

  • Fine way of telling, and nice article to get information about my presentation focus, which i am going
    to deliver in university.

  • Java was never taken away from JavaScript. It have nothing to do with Java, even that name came after it was created.

Leave a Comment

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Anti-spam image