Wednesday, October 04, 2006

Breaking Dependencies With Interfaces

I just picked up on Mike Dinowitz's post Interfaces - Why Bother? asking for benefits that interfaces offer. I started typing a comment, but realised that my two-penn'orth was way too sprawling for a comment and needed a whole post of its own. So here goes - I'm sure this won't be telling someone as experienced as Mike anything he doesn't know, but there's also been a call for more introductory-level blog posts in the last couple of days, so hopefully someone out there will find this useful as a concrete example of where I'd be lost without interfaces.

I'm currently working on a large J2EE platform with over 2000 classes. The only sensible way to manage such complexity is to split it into several distinct modules, each of which is conceptually self-contained and compiled and unit-tested separately, in a strict order. The ANT build script builds the core framework module first, then the email server, then the groups and user modules, etc etc, finally finishing up with the Tapestry-based web interface.

This all works great, except that you're still left with cross-module dependencies. The email server needs to know that group emails should be forwarded to group members, the groups module needs to know about its users, and so on.

The way these dependencies are resolved, is through interfaces. In the email module, we create an interface that represents the bit of group- or member- related functionality that the email server needs to know about - in this case, the ability to accept and propagate an email - and make the group and member objects (which come after the email server) implement that interface.

So we make an interface called EmailPropagator with one method - propagate() - in the email server module, and make groups and members implement EmailPropagator.

This way, the email server doesn't need to know anything else about the "thing" it's sending email to - so long as it implements EmailPropagator, the email server can ask it to propagate email to whoever or wherever it feels like, and that's all that the email server needs to know about it in order to do its job.

We can make anything be accepted by the email server, so long as it implements that one method. It could be a group / mailing list, an individual person, a spam demon that forwards ten thousand copies to random addresses, a black hole that just swallows it up, a file store.... anything, so long as it implements that one method.

This is part of the power of OO, and particularly the power of interfaces. It's also different from inheritance - in Java, and hence CF too, a class can only extend one base class. However, it can implement as many different interfaces as you like. You've heard of "If it looks like a duck and quacks like a duck, it must be a duck...." - well, this extends to "If it quacks like a duck, I don't care what it is, so long as I can ask it to quack".

It's one of the guiding mantras of OO that you should "design to interfaces, not implementations" and this is exactly why - we can make a million different things plug into our email server and do a million different actions with an email, without ever needing to change the email server code. That's power, and that's reusability. And that's why I love interfaces.


Anonymous said...

That's a great explaination. I'm new to OO and practical examples of concepts are good. I'm so new, I wonder what the syntax is calling methods across cfc's using interfaces would look like.

Falken said...

"in Java, and hence CF too, a class can only extend one base class. However, it can implement as many different interfaces as you like"
Except CF doesn't have interfaces.

Maybe one day Adobe will give cfcomponent 'implements' as well as 'extends' (like BlueDragon), but until then we're stuck with long dependancy chains and methods that just throw a custom AbstractException :-(

Alistair Davidson said...

@anonymous: Glad you found it useful. I think the question about syntax is more relevant to how you define the interface rather than how you call the methods. I think it could be something like this (obviously substitute { and } with <>:

{cfinterface name="MyInterface" extends="SomeOtherInterface"}
{cfinterfacemethod name="myMethod" returntype="returntype"}
{cfinterfacemethodargument name="argName" type="argtype" /}

You could then add an implements attribute to the {cfcomponent} tag that requires implementation of all the methods defined in the interface list.

However, I'm guessing that they might have to break an awful lot of legacy code to get this to work.

Vince Bonfanti said...

Alistair wrote: "I'm guessing that they might have to break an awful lot of legacy code to get this to work."

You should look at the BlueDragon 7.0 implementation of interfaces and abstract CFCs; it doesn't break any legacy code at all:

There's no reason the same (or similar) implementation in CF8 wouldn't also be fully backwards-compatible.

Alistair Davidson said...

Hi Vince,

I'm currently toying with the idea of setting up my own server, and BD7 is getting more and more tempting... am I right in thinking that the free version is single-ip, like the developer license of CF?

Anonymous said...

The fact that I won't know until runtime that a class doesn't implement an interface makes it very different from the way Java interfaces work, and drastically reduces their usefulness. They are still useful, but in essense all you are doing is substituting one runtime error message for another.

Brian Kotek

Roland said...

You don't have to wait until runtime at all. If you're running *any* kind of automated test harness (unit tests, etc), then when you kick off the test cycle these errors show up in those tests. This is invaluable if you're integrating large numbers of components from different sources or even just working on a large team.

This way, any time someone checks in a new or modified component, you don't have to manually test or run the pages - you just kick off your testing process and wait for the results. It's just another way of doing a build process.

Alistair Davidson said...

You can also automate this process if you're running a Continuous Integration tool. We use Damage Control here, but I've also heard good things about Cruise Control. There's a feature comparison here:

Kola said...

Interesting discussion. I've always liked the flexibility that interfaces provide in pretty much the same way your example illustrates. But as others point out is it needed. One question I do have is if people want interfaces and know Java why don't we see more people creating their model in Java where they can use interfaces and can make use of their existing code base and leave the web tier to ColdFusion + Fusebox or whatever...


Alistair Davidson said...

Hi Kola

That's a good point, and I've asked the same question myself many times. I know of at least one City-based company who do exactly that for their spread-betting site - they have the trading platform itself written in Java, and the web front-end is in CF - and I think it's a great way to combine the benefits of the two tools.

As for interfaces, I'm going to repost here a comment I made on Ben Nadel's blog, cause it's equally relevant:

My personal take on the strong-vs-dynamic typing issue is this - dynamic typing is one of the reasons that CF is so easy to pick up and run with, but it's also one of the things that can cause big headaches on more complex projects.

For me the main value of strong typing - and interfaces - is not so much that you can catch errors at compile time vs. runtime, but that it means you have less to worry about at design time.

On small projects, you can often keep a good picture of the whole app in your head, but as the project grows, that's just not possible, and interfaces are a very handy way to manage copmlexity.

Also, if you're getting errors in someone else's code that you've had to pick up because they're away or whatever, what's easier to trace through:

cfargument name="blah" type="Any"
(do some stuff that causes an error)


cfargument name="blah" type="ADefiniteCFCType"
(do some stuff that causes an error) least with the second, you can go straight to the CFC in question and start hunting from there.

jones said...

Nice blog...
visit also coldfusion example