Wednesday 8 May 2013

A case for interfaces in CFML

G'day:
Yesterday I slapped together a reasonably incoherent article discussing the very basics of how interfaces are implemented in CFML, and tried to give an example of where one might consider using interfaces rather than fall back to class inheritance. Basically my take on interfaces is that they define a behavioural or integration contract, rather than define what it is to be an object (which is what the CFC itself defines).


So if we have a general looping method which will iterate over a collection of objects provided the collection has a next() method (ie: our looping code calls next() on the passed-in collection), and a get() method (to fetch the current item of them collection) we can formalise this requirement thus:

// ILoopable.cfc
interface {

    boolean function next();
    any function get();

}

And then  we can say that the collection we give to our Looper must implement ILoopable. because if it does, the code in Looper can know that it can safely call next() and get() on the collection, because the collection will definitely have them:


// Looper.cfc
component {
    public Looper function init(ILoopable collection){
        variables.collection = arguments.collection;
        return this;
    }

    public function loop(required any callback){
        var result = [];
        while(variables.collection.next()){
            arrayAppend(result, callback(variables.collection.get()));
        }
        return result;
    }

}

// PersonCollection.cfc
component implements="ILoopable" {

    public PersonCollection function init(required array people){
        variables.people = arguments.people;
        return this;
    }


    public boolean function next(){
        param name="variables.personIdx" default=0;
        return ++variables.personIdx <= arrayLen(variables.people);
    }

    public any function get(){
        return variables.people[variables.personIdx];
    }

}

<cfscript>
    // testLooper.cfm
    people = new PersonCollection(["Amy","Barry","Catherine","David","Emma"]);

    looper = new Looper(people);

    function upper(s){
        return ucase(s);
    }

    result = looper.loop(upper);

    writeDump(result);
</cfscript>

End result:
array
1AMY
2BARRY
3CATHERINE
4DAVID
5EMMA


Some people disagree that interfaces in CFML have merit for a few reasons:
  • CFML is loosely typed (or, more to the point: not statically typed), so it makes no sense. Well yes and no. Loosely typed doesn't mean typeless, so there are going to be situations when the data's type is important to enforce (arguably this is almost all the time). No-one complains about having a type on an argument or a returntype on a function, so having an interface as a type is just as pertinent as any other sort of type-checking.
  • CFML is not compiled. OK, sure. In Java and C# one gets compile-time errors when interface obligations aren't met (indeed, one even gets them in the IDE, which is cool!), but... so what? I actually think CF should be able to do compile-time checking on this stuff (remember CFML is compiled for all intents and purposes before it's executed), but for some reason ColdFusion does the check at runtime. But it still does the check so what does it really matter, other than if one is to be somewhat pedantic about it?
  • Coupled to the above: ColdFusion Builder or any other CF-enabled IDE don't assist when it comes to interfaces. I see this as a shortfall in the IDE, not in the interface concept. CFB can offer code assist on CFC methods, so it should be able to parse a CFC and determine it is supposed to implement an interface but does not, and then flag a warning.
  • One can just inspect the code being used, and make sure one has all the correct methods implemented by hand. Whilst this is true, it's a bit of a leaden approach to take if one doesn't need to. To work out what methods one needs to implement when using an interface, one has precisely one place to look: in the interface. If one just relies on providing the correct behaviour, then one has to parse the code of the file using the non-interfaced object (ie: in this case Looper.cfc) and work out what methods PersonCollection needs to have, how they're used etc, and then implement them. That's quite a crappy approach to things. It also requires havnig access to the source code, as well as detecting any nuances of the usage of the methods which might not immediately be obvious: the return types, the arguments (and optional ones etc). This is easy to do with a trivial example as per above (and also given you have the source code), but this approach can be very unreliable and error prone if undertaken on serious amounts of code.
  • CFML interfaces don't implement [some feature(s)] that other languages' implementations of interfaces do. Sure. And other languages' class implementations have different  / more / better things than CFCs. But this doesn't cancel the merits of CFCs entirely, does it? Actually this was something that had people railing against CFCs back in CFMX 6.1 & 7 when they were new, but people seem to have moved on now. And not, I think, only because of atter enhancements to CFCs, they've just realised they set out to do certain things, and they do them pretty well.
Now I don't think interfaces are a panacea, and I don't think anyone is going to be using them all the time, or indeed everyone will be using them at all. But I think all the commentary I've encountered against them doesn't hold a great deal of water once one dispenses with one's prejudices (in favour of other language implementations), and force of habit development practices. And general excuse-making.

At the very least CFML dev should be aware of how interfaces work, because whether or not one thinks they have a place in CFML, it can't be argued that they do have a place in other languages which we'll be interacting with in our CFML code.

I'm actually going to leave this article at that. It's not what I intended to write today... well it's just the intro to what I was going to write, but I think it stands on its own merit, and I'm out of lunchtime and I want to press send.

I'll write some more this evening, I think. I want to look at the code side of interfaces a bit.

Hopefully this short piece will be at least some food for thought for people.

Righto.

--
Adam