Tuesday 7 May 2013

Interfaces in CFML. What are they for?

G'day:
Now this will be an interesting one. Not "interesting" as in "fascinating" but "I dunno what I'm gonna write yet, so it'll be interesting to see what I come up with". So possibly more interesting to me during the writing process that to you during the reading process.

I've just been having a conversation on Twitter about interfaces (ie: the <cfinterface> kind), and I recalled that at some stage I was gonna have a look at them in depth to completely get my brain around them. This notion predates this blog, but it's been something I've meant to cover at some stage.


OK, so what's an interface? TBH, reading that Wikipedia link I posted above didn't leave me too much the wiser as it's a bit too theoretical rather than practical. Also reading a lot of the example material on the 'net often left me going [shrug] too.

Technically, an interface defines some rules than any class (read: CFC in a CFML context) that implements that interface must implement. Here's an very neutral and meaningless example:

// IDemo.cfc
interface {

    numeric function f(required string s, boolean b);

}

// Demo.cfc
component implements="IDemo"{
}

<!--- testDemo.cfm --->
<cfset demo = new Demo()>

If I run this, I get an error:

CFC blog.interfaces.Demo does not implement the interface blog.interfaces.IDemo.

The f method is not implemented by the component or it is declared as private.

Here IDemo.cfc says "if you implement me, you gotta provide an f() method, like so". And Demo.cfc implements IDemo,  but doesn't actually define a method f(). So we get the error.

Furthermore, not only does the method need to exist, it needs to match the method signature in the interface, as demonstrated with a slightly tweaked Demo.cfc:

// Demo.cfc
component implements="IDemo"{

    function f(){
    }

}

This gives a different error:

Return type mismatch.

The f function does not specify the same return type in the blog.interfaces.Demo ColdFusion component and the blog.interfaces.IDemo ColdFusion interface.

And unless we have the arguments the same too, we still get an error:

// Demo.cfc
component implements="IDemo"{

    numeric function f(){

    }

}

Function argument mismatch.

The f function does not specify the same arguments or arguments in the same order in the blog.interfaces.Demo ColdFusion component and the blog.interfaces.IDemo ColdFusion interface.

Note I even have to make sure I specify the optional argument definition:

// Demo.cfc
component implements="IDemo"{

    numeric function f(required string s){

    }

}

(yields the same error as previous)

To get the code to work at all, I need to have exactly the same method signature:

// Demo.cfc
component implements="IDemo"{

    numeric function f(required string s, boolean b){

    }

}

Also note here that I am getting these errors even without trying to call f(). Just attempting to create the object fails. If I tried to call the function above I'll get a different runtime error, because its implementation is supposed to return a numeric, but it doesn't return anything:

<!--- testDemo.cfm --->
<cfset demo = new Demo()>
<cfset result = demo.f("")>

Errors with:

The value returned from the f function is not of type numeric.

If the component name is specified as a return type, it is possible that either a definition file for the component cannot be found or is not accessible.

I'm belabouring this slightly because I want to show that the interface requirement is just on the method signature, not on what the method actually does. So IDemo.cfc says "you gotta implement a function f which takes a string s and might take a boolean b and returns a numeric. How you actually effect this is up to you, but those are the rules I require if you're gonna implement me".

Those are the basic mechanics of interface implementation. There's other stuff to consider like interface inheritance, implementing multiple interfaces and specifying a argument or a return type as being an interface, which I'll get to. But that's the baseline of what an interface "does".

OK, but why?

This is where really a lot of examples online let themselves down. I often see examples like this:

// IAnimal.cfc
interface {

    numeric function countLegs();

    boolean function canSee();

}

// Dog.cfc
component implements="IAnimal" {

    public numeric function countLegs(){
        return 4;
    }

    public boolean function canSee(){
        return true;
    }

}

// Worm.cfc
component implements="IAnimal" {

    public numeric function countLegs(){
        return 0;
    }

    public boolean function canSee(){
        return false;
    }

}

So here's an interface describing what it is to be an animal (countLegs() and canSee()), and a Dog and a Worm. Yes, fine, technically speaking that's an example of implementing an interface.

However it does leave the question as to why we're using IAnimal instead of an abstract class Animal. Obviously we're straying from CFML here as CFML doesn't have abstract classes, but most of the examples of interfaces on the 'net are using languages which could implement an abstract class of Animal instead of an interface. And it's a reasonable question as to why we don't just use class inheritance here, so it's not exactly a clear example of why / when one would use an interface.

Perhaps a valid CFML example here using component inheritance (and emulating abstract classes) could be:

// Animal.cfc
component {

    numeric function countLegs(){
        throw(type="MethodNotImplementedException");
    }

    boolean function canSee(){
        throw(type="MethodNotImplementedException");
    }
}

// Cat.cfc
component extends="Animal" {

    numeric function countLegs(){
        return 4;
    }

}

<!--- testCat.cfm --->
<cfset cat = new Cat()>
<cfoutput>
    Legs: #cat.countLegs()#<br>
    Can see: #cat.canSee()#<br>
</cfoutput>

This outputs:

Legs: 4
Can see:
The web site you are accessing has experienced an unexpected error.
Please contact the website administrator. 


The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request


The error occurred inD:\websites\www.scribble.local\blog\interfaces\Animal.cfc: line 9
7 : 
8 :  boolean function canSee(){
9 :   throw(type="MethodNotImplementedException");
10 :  }
11 : }

That's a bit jerry-built, but it demonstrates that there's some sense behind having an Animal class rather than an IAnimal interface.

One validation these examples offer to justify using interfaces is that in Java (and in CFML) one has only single-inheritance, so using interfaces allows one to emulate multiple inheritance (a component can implement as many interfaces as it likes). But in these situations it's a contrivance rather than a good example (he says, having example classes "Dog" and "Worm", which is taking the piss as far as contrived situations go!).

I think the problem is that all the interface examples use noun-oriented interface names, eg: Vehicle, Animal, Person, etc. Nouns suggest classes. To me interfaces come into their own when thinking in adjectives. Stuff like countable, or iterable, or serialisable.

An easy example is an iterator. It iterates over stuff. And to do that, it needs to call next() on whatever it iterates over. It doesn't need to do anything other than that, and it'll work on any sort of object as long as the object has that next() method. And it doesn't care the class of the object it's iterating over one bit. It only cares that it has a next() method. So we have this:

// IIterable.cfc
interface {

    boolean function next();

}

// NumericSequence.cfc
component implements="IIterable" {

    public NumericSequence function init(required array elements){
        variables.elements = arguments.elements;
        variables.current = 0;
        return this;
    }

    public boolean function next(){
        variables.current++;
        return variables.current <= arrayLen(variables.elements);
    }

    public any function get(){
        return variables.elements[variables.current];
    }

}

<cfscript>
    // testNumericSequence.cfm
    numbers = new NumericSequence(["tahi", "rua", "toru", "wha"]);

    while(numbers.next()){
        writeOutput(numbers.get() & "<br>");
    }
</cfscript>

And this outputs:

tahi
rua
toru
wha

The interface just says "to be iterable, you need to implement next()", but other than that, the interface doesn't dictate anything about the class at all. So any class can be "iterable" simply by having a next() method, eg:

// Calendar.cfc
component implements="IIterable" {

    public Calendar function init(required date start, required date end){
        variables.start = arguments.start;
        variables.end = arguments.end;
        variables.current = variables.start;
        return this;
    }

    public boolean function next(){
        variables.current = dateAdd("d", 1, variables.current);
        return dateCompare(variables.current, variables.end) <= 0;
    }

    public date function get(){
        return dateFormat(variables.current, "yyyy-mm-dd");
    }

}

<cfscript>
    start    = now();
    end        = dateAdd("d", 7, now());
    calendar= new Calendar(start, end);

    do {
        writeOutput(calendar.get() & "<br>");
    } while (calendar.next());

</cfscript>

Outputs:

2013-05-07
2013-05-08
2013-05-09
2013-05-10
2013-05-11
2013-05-12
2013-05-13
2013-05-14

So here a NumericSequence and a Calendar are quite different classes, and their general behaviour and usage will be very different, but they both implement IIterable, so they can be iterated over.

Still: the utility of this is minimal. There's no gain in using an interface for these two examples, because either the component has a next() method or it does not. The calling code doesn't need to know about any interface requirements, it just needs to know how the next() method works, which we know from the component's own API.

But what if it's not our own code we're using. Consider a utility component that takes a collection and outputs it as an ordered list:

// IListable.cfc
interface {
    boolean function next();
    string function asString();
}

// UnorderedList.cfc
component {

    public UnorderedList function init(IListable collection) {
        variables.collection = arguments.collection;
        return this;
    }

    public string function toList(){
        var list = "<ul>";

        do {
            list &= "<li>#variables.collection.asString()#</li>";
        } while(variables.collection.next());

        var list &= "</ul>";
        return list;
    }

}

// ColourCollection.cfc
component implements="IListable" {

        public ColourCollection function init(required array colours){
        variables.colours    = arguments.colours;
        variables.current    = 1;
        return this;
    }

    boolean function next(){
        return ++variables.current <= arrayLen(variables.colours);
    }

    string function asString(){
        return variables.colours[variables.current];
    }

}

<cfscript>
    colourCollection = new ColourCollection(["Whero","Karaka","Kowhai","Kakariki","Kikorangi","Tawatawa","Mawhero"]);
    colourList = new UnorderedList(colourCollection);
    writeOutput(colourList.toList());
</cfscript>

Here we have a generic CFC UnorderedList which outputs a collection as an unordered list. Its only requirement of the collection it processes is that the collection implements IListable, which specifies a next() and an asString() method. Because it only accepts collections which fulfil that contract, it can happily assume those methods exist, and use them. Or put the other way, to use UnorderedList.cfc, you know your collection will need to implement these methods. And as long as they do what the interface says, you know UnorderedList will produce a list from your collection.

And if you try to pass it something that isn't IListable, you'll get an error right there and then with the method call (the init() method in this case). It won't only happen when it gets into the bowels of toList() and tries to call next() only discover the collection object doesn't have a next() method. This makes troubleshooting easier. Especially if you don't have the source code of the UnorderedList.cfc, so can't see why the thing actually errored.

You might think that this still suggests an inheritance situation, in which the ColourCollection is a type of Listable. But it's not a "Listable". What is "a Listable"? The interface describes a subset of behaviour, it does not describe all the behaviour and it does not describe the type of object in general. I could make a completely different component than implements IListable, and there's no relationship between it and ColourCollection at all:

// JunkPile.cfc
component implements="IListable" {

    public JunkPile function init(required struct stuff){
        variables.stuff    = arguments.stuff;
        variables.elements    = structKeyArray(variables.stuff);
        variables.currentElement = 1;
        return this;
    }

    boolean function next(){
        param name="variables.currentElement" default=1;
        return ++variables.currentElement <= arrayLen(variables.elements);
    }

    string function asString(){
        var thisElement = variables.stuff[variables.elements[variables.currentElement]];
        if (isSimpleValue(thisElement)){
            return thisElement;
        }else{
            return serializeJson(thisElement);
        }
    }

}

There is no sensible common type that JunkPile.cfc and ColourCollection.cfc are both types of.

The same works in reverse with returntypes from methods. A method can return a type of [some interface], for similar situations in which your code doesn't really care what sort of object it is you need to work with, just as long as you know it has methodX() or methodY(), etc, because that's all you code needs to call. It just adds flexibility in that you're not locked down to using objects of specific types, meaning if suddenly you want that code to work with a different object type (which might already extend something else), all you need to do is to implement the methods that the interface requires, and add 'implements="ITheInterface"' to the component definition. This - on the whole - is an easy tweak, and doesn't require touching the rest of the code in the component.

There's a chunk of other things to discuss on interfaces, but this is probably a good place to close this article off. Hopefully I've clarified why interfaces can be handy, even in CFML. It's the sort of thing that can be worked around without using interfaces, but it is an industry-standard approach to effecting this sort of thing, so is perhaps a good idea to follow suit, rather than implement your own hacked-together approach to a problem that has a well-defined and accepted solution.

Interfaces are by no means something one will use every day, and for a lot of the sort of code one might write in CFML, they're never needed at all. But there is a case for them.

Comments / questions  / corrections, as always, welcomed.

--
Adam