Let’s define a simple interface.
-
var Duck = {
-
fly : function() {},
-
swim : function() {},
-
quack : function() {},
-
color : ""
-
}
And a class that implements this interface:
-
var Mallard = function() {};
-
Mallard.prototype.fly = function() {
-
alert("fly!");
-
};
-
Mallard.prototype.swim = function() {
-
alert("swim!");
-
};
-
Mallard.prototype.quack = function() {
-
alert("quack!");
-
};
-
Mallard.prototype.color = "White, Brown and Green";
So now, how do we let the browser know that Mallard needs to implement Duck? Javascript allows you to loop through the properties of any given object using some simple reflection:
-
var UTIL.impl = function(objectToCheck,interfaceToImplement) {
-
for(var property in interfaceToImplement) { // loop through the properties in the interface
-
if(typeof objectToCheck[property] !== typeof interfaceToImplement[property]) {
-
alert("Bad developer! Object does not conform to the interface.");
-
}
-
}
-
};
So, we’ve got our object, Mallard, which should implement the interface Duck. We’ve got our method impl in a general purpose utility bucket, UTIL. The for loop here loops through every property of interfaceToImplement, setting property to the string identifier of the current property. One of the iterations, if called as UTIL.impl(Mallard,Duck) would set property equal to “fly”.
How do we enforce the contract? simple! Let’s modify the constructor of Mallard a little:
-
var Mallard = function() {
-
UTIL.impl(this,Duck);
-
}
-
/* snipped out property declarations */
So now we get alerted if our Mallard object ceases to conform to the Duck contract. That’s pretty neat, but we can go one step further by changing our impl method:
-
Object.prototype.impl = function(interfaceToImplement) {
-
for(var property in interfaceToImplement) {
-
if(typeof this[property] !== typeof interfaceToImplement[property]) {
-
throw new Error("Bad developer! Object does not conform to the interface.");
-
return false;
-
}
-
}
-
return true;
-
};
Since we’re extending the base Object type to include a method impl, any object we create will have access to the impl function. This has the added advantage of being able to typecheck incoming arguments to the interface. We’re also throwing an error instead of displaying a pesky alert box.
An example of typechecking in action:
-
function scareDuck(duckObject) {
-
if(duckObject.impl(Duck)) {
-
duckObject.quack();
-
}
-
}
This is pretty helpful, since Javascript isn’t a strongly typed language. We can always be sure we’re executing methods on the correct kind of object, this way. It also allows for safe polymorphism - duckObject.impl(Duck) does not care what other methods or properties duckObject has, only that it correctly satisfies the Duck contract.
With that out of the way, we can now change our Mallard constructor to something that makes a little more sense:
-
var Mallard = function() {
-
this.impl(Duck); // will throw an error if Mallard does not correctly implement <strong>Duck</strong>.
-
}
-
/* snip */
Go forth and implement! To further enhance your Javascript applications, take a look at my preferred javascript inheritance method.
Tags: Interface, JavaScript, Programming