Blog

teaching javascript the concept of interfaces

Posted by:

There’s an easy way to simulate Interfaces in Javascript, this post shows you how it is done.

(Beware, I’m going to mix OO terms in this posts a little bit for the sake of Javascript, but readers familiar with OO concepts will understand what I mean.)

At first we will extend Javascript’s native Object with a method called __implements, which is responsible for comparing class-methods with method-stubs (declared as strings) in an “interface” (quoted, because it is not an interface as we know it from Java). For the interface itself we will use a numeric array – it’s values are the methods which have to be implemented by the class that implements the interface itself. Since Javascript is loosely typed, we’ll have neither method signatures nor parameter lists in there.
(Of course, they can and should be mentioned in the according docBlocks!)

The following code-snippet shows the Object.__implements() method:

JAVASCRIPT:

  1. /**

  2. * Extend the Javascript native Object-class(doh!) with a function that registers

  3. * the names of the methods to implement to the protoype of the the object that

  4. * is about to implement the specified methods.

  5. *

  6. * The function needs at least 2 arguments. If more than 2 arguments have been found,

  7. * the interfaces will be added subsequently to the prototype of destination.

  8. *

  9. * Example:

  10. *

  11. * <code>

  12. * // MyObject must implement MyInterface1 and MyInterface2

  13. * Object.__implements(MyObject, MyInterface1, MyInterface2);

  14. * </code>

  15. *

  16. * @param Function destination The Javascript-Function (<code>prototype</code>) that

  17. * should “implement” <code>source</code>

  18. * @param Array source A numeric indexed array, whereas the values are the method-names

  19. * destination should provide fully and properly implemented.

  20. */

  21. Object.__implements = function(destination, source)

  22. {

  23. // check wether the __interface-property has already been defined

  24. // for destination

  25. if (!destination.prototype.__interface) {

  26. destination.prototype.__interface = new Array();

  27. }

  28. // get the interfaces “destination” has to implement

  29. // and add it to __interface as an associative array

  30. for (var a = 1, max_a = arguments.length; a <max_a; a++) {

  31. source = arguments[a];

  32. for (var i in source) {

  33. destination.property.__interface[source[i]] = true;

  34. }

  35. }

  36. }

In the above code we are preparing the __interface-property to hold the method names defined in the values of the array(s) which was/were passed to the __implements-method (beware: implements is a keyword in Javascript so we may not use that directly). The __interface-property will be checked later on against the method-names defined in destination.

In the following example we have our interface-definition DefaultTableModelListener (which is nothing more than an array holding method names) that will be implemented by Table – thus an instance of Table will work as as TableModelListener. Notice, that comparing Table with DefaultTableModelListener using the instanceof-Operator will not work, since DefaultTableModelListener is a simple variable declared as an array in the namespace. As a workaround, you could extend Object again with your own __typeof()-method holding the appropriate logic, but for now we will focus on __implements().

Before we can finish our little brainstorm, we have do two other things. First off, we need a mechanism that checks the class against the interface-methods AFTER the class has been defined. We can do this as soon as an instance has been initialized using the new keyword with another method we have to write, or we could call __implements after the class has been registered (__implements() will then be called during script runtime, that is, when the class exists in the memory and __implements can check it’s defined methods); we’ll stick to option no. 1 – usually you want to see the class bindings before you implement it. One flaw is that the integrity is checked each time you instantiate an object of a class that was told to use an interface. However, a cache could solve this problem.

The following code-snippet follows a similiar approach as protoype does, so this might be familiar to you:

JAVASCRIPT:

  1. var Class = {

  2. prepare: function() {

  3. return funcion() {

  4. // check wether the class should implement an interface

  5. if (this.__interface) {

  6. // __interface property available, check integrity

  7. for (var i in this.interface) {

  8. if (!this.i) {

  9. throw(i+” was not defined!”)

  10. }

  11. }

  12. }

  13. this.construct.apply(this, arguments);

  14. }

  15. }

  16. };

Class.prepare can then be used to work as a constructor. As soon as you create an object using new, the interface defintion will be checked, if available.
Also, the prototype the return value gets assigned to should have a method called construct available, which will also be called upon constructing an instance of this object (thus the name).

Below is a full example of what we have so far.

JAVASCRIPT:

  1. /**

  2. * (Our interface definition. We chose an array but this could be any other datatype,

  3. * just make sure the function Object.__implements and Class.prepare

  4. * respond properly to it. )

  5. *

  6. * A TableModelListener can register with a TableModel to receive

  7. * notifications of changes to the model.

  8. */

  9. TableModelListener = new Array(

  10. /**

  11. * This method is called to indicate that a column has been added to the model.

  12. *

  13. * @param integer index The position in the model where the column has been added

  14. */

  15. “columnAdded”,

  16. /**

  17. * This method is called to indicate that a column has been moved.

  18. *

  19. * @param integer index The position in the view where the column has been moved to.

  20. */

  21. “columnMoved”

  22. );

  23. var Table = Class.prepare();

  24. /**

  25. * Table must implement columnAdded and ColumnMoved

  26. */

  27. Object.__implements(Table, TableModelListener);

  28. // Define methods for the Class Table here. Do not use “Table.prototyp = {“

  29. // as it will overwrite __interface which has been declared before for this prototype

  30. /**

  31. * Constructor. Will be called as soon as an instance of this class is being created using “new”

  32. */

  33. Table.protoype.construct = function()

  34. {

  35. alert(“Constructor called”);

  36. }

  37. /**

  38. * columnAdded as defined in TableModelListener. Does not much in this default implementation.

  39. */

  40. Table.protoype.columnAdded = function()

  41. {

  42. alert(“columnAdded called”);

  43. }

  44. // this should throw an error, since we do not have columnMoved defined in the Table-class.

  45. var table = new Table();

So we can define interfaces and rely on the availability of their methods in the classes that implement them. This might look like filling the userland with some extra-bloat since we are missing type hints and your usual instanceof does not tell you if the class is also an instance of the interface (since the interface in our example is not recognized as an object). A simple workaround comes in mind with extending the interface-like array with a property that hold it’s name, assigning this to a prototype-property and let a userland written typeof() check if this property matches the name of the interface (theoretically, this would also make multiple inheritance possible).
Another option would be to use a Javascript parser (for example J4P5 for PHP5) that “compiles” Javascript before browser-runtime and checks dependencies and triggers errors if an interface declaration was not properly implemented by a specific class.

Anyway, this could be your new little buddy when it comes to programming larger Javascript applications where interface-like concepts are needed.

1


About the Author

Thorsten is the author of the conjoon open source project and the Ext.ux.Livegrid component. In this blog he writes more or less frequently about his current projects and web development in general.

Discussion

  1. Arseniy  December 26, 2007

    Someone forwarded me your EXT large sets of data in a grid example, I thought hmm, not bad. Then I ran into this article and the first thing I find is “At first we will extend Javascript’s native Object with a method called __implements”

    a big no no. My I suggest, but never, ever, ever ever ever extend basic objects within Javascript. You forget the FASTEST statement is “for each” and it will find __implements in any Object of javascript and this is REALLY bad. The one crack-cocaine of javascript is the prototype framework. Everybody loves it but I say hate it, one time someone told me “I love prototype because it has support for Hash object”, well the REASON it has that support is because the creator doesn’t know that an Object is a hash in the first place OR he could’nt implement it using the Object because he couldn’t perform a for each operation, so he had to write his own wrapper, which eventually slows the hell out of the Hash concept, since you have an overhead of a few function calls while working with every operation of the hash.

    (reply)

Add a Comment


Refresh