One of my readers hit me up with a question about interfaces in CFML:
[...] I would like to ask you a question please. I was unable to find a recent good article by you or anyone else about this topic.
ColdFusion Interfaces; do you rekon using them or not please? Please provide why you would not reckon using ColdFusion Interfaces. [...]
I started looking at this yesterday which explains yesterday's blog post, but I got sick of writing before I had a chance to actually answer. Now I'm sitting at my folks' place (sans rat, today), in the middle of a power outage. Fortunately my battery is charged up, and I can answer this one without any research, so here we go.
Firstly some background: what's an interface? Interfaces play a part in inheritance and type checking on OO. Or perhaps "suitability checking" would be a better way of putting it.
Basic inheritance in OO is achieved by a subclass extending a superclass so that it can refine the behaviour of the parent class for a more specific / less general type of the super class. The overused example is that of a Person and an Employee. The class Person might define that a person has a
firstName
and a lastName
:// Person.cfc
component accessors=true {
property firstName;
proeprty lastName;
function init(firstName, lastName){
setFirstName(firstName);
setLastName(lastName);
}
function getFullName(){
return firstName & " " & lastName;
}
}
That's a reasonable implementation of a Person. An Employee is definitely a Person, but it is a specific type of Person: in this case on that has an
employeeNumber
as well as everything else:// Employee.cfc
component extends="Person" {
property employeeId;
function init(firstName, lastName, employeeId){
supier.init(firstName, lastName);
setEmployeeId(employeeId);
}
}
So that's inheritance. Note that in CFML one can have as deep a hierarchy of inheritance as one likes (beyond a couple you're probably doing it wrong though), but a given class can only extend one other class. CFML uses single inheritance. Some languages permit multiple inheritance, where a given class can be two or more different things at once. An Employee might be both a Person and a Resource (for scheduling meetings or consultancy or something).
How can one solve the notion of an Employee being both a Person and a Resource? This is where interfaces comes in. In this situation I'm going to decide my Employees are more People than they are Resources, so I stick with the current inheritance model. I also decide that rather than being an actual class of object, a Resource is more of a usage trait. A resource can be booked, and perhaps a resource can be billed for. At the same time, a Resource is not necessarily just a Person, it could be a Room, or it could be a Car. But all resources have two definite behaviours: they can be booked, and they can be billed. So we create an interface:
// Resource.cfc
interface {
function book(dateFrom, dateTo);
function getRateForBilling();
}
(this example is far from good architecture, I know).
And now we can say that an Employee is a type of Person (inheritance), and can be used as a Resource (via the Resource interface):
// Employee.cfc
component extends="Person" implements="Resource" {
property employeeId;
function init(firstName, lastName, employeeId){
supier.init(firstName, lastName);
setEmployeeId(employeeId);
}
function book(dateFrom, dateTo){
// add an entry to the Employee's calendar or something
}
function getRateForBilling(){
// fish this rate out of somewhere
}
}
I'm being purposely vague in the implementation of the interface here, because as far as the interface is concerned all it cares about is that those methods are implemented. Any other method can be implemented as well, but the contract is that in situations where a Resource is called for, then something implementing Resource must (must) implement those methods.
How is this useful?
Well it allows for a kind of polymorphism.
Let's say we have a Meeting class:
// Meeting.cfc
component {
function create(startTime, endTime, Resource[] resources){
resources.each(function(resource){
resource.book(startTime, endTime)
});
}
}
I have to admit I am not sure if CFML will allow an argument to be an array of objects of a specific interface type, so perhaps treat this as pseudocode. The point being that
create()
expects an array of Resources, and if the elements are Resources, then one will get an error on the method call: "oi, these need to be resources". They don't need to be Employees, or Rooms, or Cars (unlikely for a meeting, I know), all the function needs to be sure of is that they are all Resources. Because it needs to be able to call the Resource's book()
method, to book the Resource for the Meeting.That's it: an interface is basically a declaration than a class does indeed implement a specific subset of behaviour.
One other real-world use is when using dependency injection in conjunction with testing. If you're using DI and TDD, then one could use interfaces to type check the dependency, rather than a concrete class type. Why? So the dependency can be easily mocked out when testing. EG:
// PersonService.cfc
component {
property dao;
function init(PersonDao dao){
// etc
}
function load(id){
var person = dao.load(id);
// etc
}
}
// PersonDao.cfc
interface {
function load(id);
}
// PersonDaoImplementation.cfc
component implement="PersonDao" {
function load(id){
// actual code to hit the DB and get the Person
}
}
// PersonDaoTest.cfc
component implement="PersonDao" {
function load(id){
// stub functionality to return a mocked person
}
}
// PersonServiceTest.cfc
component extends="SomeTestSystem" {
function setup(){
personServiceToTestWith = new PersonService(new PersonDaoTest());
}
}
That said, with CFML one can just use MockBox to take care of the mocking. With other languages - especially strongly and statically typed ones - mocking is less possible, so one kinda needs to provide an actual object to use as a dependency. If one uses an interface here, it's easier to create a mock that simply fulfils the interface. Otherwise one needs to use inheritance, having the mocked dependency extend the real dependency (which is bad inheritance, as a mocked dependency pretty much fails the "is a" rule of inheritance) so that it passes the type checking rules.
The Mockbox comment kinda drives a point home here... whilst interfaces are a necessity on strongly and statically typed languages, their benefits are reduced in a loosely and dynamically typed language.
First things first: there's absolutely no requirement to do type checking in CFML. So there's never a need to use interfaces in one's own code. They're simply work for the sake of it. Let's have a look at that PersonService again:
// PersonService.cfc
component {
property dao;
function init(PersonDao dao){
// etc
}
function load(id){
var person = dao.load(id);
// etc
}
}
There's no need to type check the
dao
dependency. So just don't!function init(PersonDao dao){
// etc
}
The only thing we need of the
dao
object is that it has a load()
method. Do you know how we can find that out? If the code calling dao.load()
method runs and does what you want... well then it was OK. We don't need to dance around the place applying rules for the sake of having rules to follow.Indeed the way interfaces have been implemented in Lucee and to a lesser extent ColdFusion are kind of counter to the spirit of CFML anyhow. I discussed this in yesterday's article: "CFML: Interface fulfilment via mixin". CFML is a dynamic language, so there is plenty of ways that a given object can be populated with functions (mixins, direct method injection, etc), so when Lucee type-checks an object when it's first created, it is nullifying that integral part of how CFML works. ColdFusion does a lot better job, but even then it gets too rigid with its type-checking. Bottom line, provided an object provides all the methods that an interface demands at the time the methods are needed: job done.
If I have some function which requires an argument of interface
Foo
, and what Foo
defines is that there is a method bar()
... as long as the object being passed has a method bar()
, then that is fulfilling the interface. It doesn't matter when or how bar()
was defined, or how it came to be present in the Foo
object... it just matters that it's there, ready to be called. The object of the exercise here is to ensure the behaviour is provided, not that the argument type matches the "implements" value in the component definition.Given Lucee and ColdFusion mess this up, I say it makes CFML's interface implementation more problematic that its worth, so don't use 'em.
What about if you're using third party code (like a framework) that uses a CFML interfaces? Well... make sure your objects implement the methods the interface needs (otherwise yer code will break ;-)... and turn off CFC type checking. Job done.
If the vendors implemented interfaces to be a truly dynamically "use time" (as opposed to just "run time" when contrast with "compile time"), then they might have a point. But really... it's just imposing rules for the hell of it, isn't it?
I think the rationale behind interfaces is a good one - using behaviour to define an object's suitability for a purpose - but really only in strongly typed languages. In CFML, provided the functionality is there, there's no need to have a rule to enforce it.
Hopefully that answers the question.
--
Adam