Interfaces in Action: the Observer/Event Listener Design Pattern
As I began working on a DirectoryWatcher example to demonstrate how the new BlueDragon 7.0 CFTHREAD tag can be used to implement Event Gateways, I realized that this example also presents a great opportunity to demonstrate one use for CFC Interfaces. I'll talk more about the DirectoryWatcher and CFTHREAD in a future blog entry, but would like to focus on its use of CFC Interfaces right now; in the mean time, you can download the DirectoryWatcher code here.
The Observer Pattern (which is also known as the Event Listener Pattern, and is sometimes referred to as Publish/Subscribe), is the first pattern discussed in Head First Design Patterns (Freeman), which says (p. 37) that: "The Observer Pattern is one of the most heavily used patterns in the Java Development Kit (JDK), and it's incredibly useful." The Observer is also one of the original patterns described in Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, et al), which formally introduced the concept of design patterns in software engineering when it was first published in 1995.
The basic idea is that some object--known as the Subject or Publisher--is going to produce events or notifications when certain things happen. Other objects--known as Observers or Listeners--can register with the Subject to be notified when these events occur. In fact, you may already be familiar with an implementation of this design pattern in CFML if you use Application.cfc, in which case your Application.cfc is the Listener. The presence of an Application.cfc in a directory is sufficient to register with BlueDragon or CFMX (the Subject) for application event notifications. When an "application start" event happens, for example, your Application.cfc is notified via its onApplicationStart event handler.
In the DirectoryWatcher example, the DirectoryWatcher is the Subject; conceptually, you can think of the DirectoryWatcher as watching the entire file system at all times and producing event notifications whenever a file is added, modified, or deleted. You can create a CFC to register with the DirectoryWatcher to receive event notifications for specific directories, and this is where CFC Interfaces come in. First, we need to create a DirectoryWatcher instance; in the example, this is created within the onApplicationStart function of Application.cfc, which is a pretty good place to do it:
<cfset directoryWatcher=createObject("component","DirectoryWatcher").init()>
Now we need to create a CFC that we can register with the DirectoryWatcher to receive event notifications. But what events does the DirectoryWatcher produce? What functions should our CFC implement and what are the function signatures? Here's the DirectoryWatcher function used to register event listeners:
<cffunction name="addListener" access="public" returntype="void">
<cfargument name="directoryPath" type="string" required="true">
<cfargument name="listener" type="DirectoryListener" required="true">
...
</cffunction>
Hmmm...the second argument to the addListener function is a CFC of type DirectoryListener. That's a clue. Let's take a look at DirectoryListener.cfc:
<cfcomponent type="interface">
<cffunction name="onAdd" access="public" returntype="void">
<cfargument name="fileName" type="string" required="true">
<cfargument name="lastModified" type="date" required="true">
</cffunction>
<cffunction name="onChange" access="public" returntype="void">
<cfargument name="fileName" type="string" required="true">
<cfargument name="lastModified" type="date" required="true">
</cffunction>
<cffunction name="onDelete" access="public" returntype="void">
<cfargument name="fileName" type="string" required="true">
</cffunction> </cfcomponent>
That's right--DirectoryListener defines an interface that tells me exactly what event notifications I can receive from the DirectoryWatcher, what functions I need to implement to receive those notifications, and what the function signatures are. I don't want to go into the details of its implementation yet, but in our example, the ImageListener implements the DirectoryListener interface:
<cfcomponent implements="DirectoryListener">
...
</cfcomponent>
We can now create an ImageListener and register it with the DirectoryWatcher to receive event notifications for any directory (as in the following code, we can register a single ImageListener to receive notifications for multiple directories):
<cfscript>
imageListener = createObject( "component", "ImageListener" );
directoryWatcher.addListener( "C:\temp\images\", imageListener );
directoryWatcher.addListener( "C:\uploads\images\", imageListener );
</cfscript>
We're able to pass an ImageListener to the addListener function because it satisfies the contract of that function as specified in the CFARGUMENT tag TYPE="DirectoryListener" attribute: ImageListener implements the DirectoryListener interface. This is what interfaces are for: to define, document, and enforce the contracts between interacting objects and their functions.
At this point, my Duck Typing, anti-Interfaces friends might raise an objection: but the DirectoryWatcher example can be implemented without using interfaces! Well, yes, it can (after all, Application.cfc doesn't use interfaces). There are two ways this example could be modified to not use interfaces:
- Instead of defining it as an interface, the DirectoryListener can just be a "regular" CFC. Then, the ImageListener would subclass (extend) DirectoryListener and everything will be fine. In some cases this is a reasonable alternative. But there are two drawbacks to this approach:
- what if ImageListener is already defined to extend some other class? CFCs only support single inheritance, so ImageListener can't extend both DirectoryListener and some other class; this approach is simply more restrictive than necessary; and,
- ImageListener doesn't actually inherit any behavior (functions) or state (variables) from DirectoryListener, so forcing ImageListener to extend DirectoryListener is really a misuse of subclassing.
- what if ImageListener is already defined to extend some other class? CFCs only support single inheritance, so ImageListener can't extend both DirectoryListener and some other class; this approach is simply more restrictive than necessary; and,
- Use Duck Typing by changing the second CFARGUMENT of the DirectoryWatcher addListener function to TYPE="ANY", and provide documentation that tells you what functions the event listeners need to implement. This could work, but I prefer the interface-based solution:
- I like for my CFCs and their functions to be self-documenting; what does TYPE="ANY" in a CFARGUMENT tag tell me? It tells me a lie, really, because in fact it doesn't expect "ANY" argument, it very specifically expects a CFC that implements the onAdd, onChange, and onDelete functions, and my code won't work properly if I give it anything else.
- I like to have the contracts between my objects enforced as much as possible and as soon as possible by the compiler and runtime. By using interfaces, I'll get an error as soon as I try to pass a CFC that doesn't implement the required functions; by using Duck Typing I'll only get an error when I actually try to call one of the functions and it doesn't exist, if I get any error at all. (Here's an example: imagine I mistype my Application.cfc "onRequestStart" function to be "onReqeustStart"? If Application.cfc had been implemented with interfaces, I'd get an error right away that the onRequestStart function must be implemented; as it stands now, with Duck Typing, all I'll know is that my onRequestStart function isn't getting called, and now I'm off digging trying to figure out why).
- I like for my CFCs and their functions to be self-documenting; what does TYPE="ANY" in a CFARGUMENT tag tell me? It tells me a lie, really, because in fact it doesn't expect "ANY" argument, it very specifically expects a CFC that implements the onAdd, onChange, and onDelete functions, and my code won't work properly if I give it anything else.
This is a very simple example of the usefulness of CFC Interfaces when implementing the Observer/Event Listener design pattern (which you'll want to do whenever you implement asynchronous gateway-type features using CFTHREAD). I'm going to follow-up with a detailed look at the DirectoryWatcher example, focusing on its use of CFTHREAD. Then I'll see if I can follow-up with some more detailed interface examples, and a few examples for Abstract CFCs (here's a hint: nearly all of the design patterns in the books referenced above are based on interfaces or abstract classes, or both; Singleton is the only pattern I can find that doesn't require either).


Nice posting! It is great to see posts showing exactly how and why people would choose to use interfaces together with the specific benefits you get from interfaces - even in a non-compiled language.
Obviously comprehensive unit tests would pick up the duck typed error but it is quite reasonable to point out that comprehensive unit tests are like bug free code - an ideal, not the normal state of programming development!
As you know, I don't like using interfaces as I do a lot of base class work which requires ANY typing all over the place to work and I feel the shorter, more simpler code trumps the benefits of interfaces, but there is a reason why so many people use interfaces in Java and .NET and while there is less benefit in CF, you have highlighted nicely a perfect example of where an interface would definitely pick up an error at run time whereas duck typed code wouldn't fail unless you exercised the appropriate code path.
I'm not sure that I agree with the other patterns REQUIRING interfaces or abstract classes. I use factories, decorators, strategy and a bunch of other patterns and manage to implement them without requiring interfaces or abstract classes.
Thanks for your comment, and for the blog entry pointing this way.
Yes, "required" is probably too strong a word there; "defined in terms of" is probably better.
Vince
The Strategy Pattern comes first.
Of course, the Strategy Pattern is also defined using interfaces. :-)
The book examples all use Java as the language of choice, so the use of interfaces in their examples is not a surprise.
My impression of the first chapter is that they were talking about "Coding to an interface" they were speaking in terms of "Code to an API" not in terms of Java Interfaces.
Don't get too hung up on "interfaces" being a Java-only or even an invented-by-Java concept--they're neither. For example, the original "Design Patterns" book I reference in my blog entry was published in 1995 before Java was announced publicly. All of the design patterns in that book (minus Singleton) are defined in terms of interfaces and abstract classes, and most (maybe all?) of the implementation examples are either in C++ or SmallTalk.
I was under the impression that C++ did not have interfaces. Is that incorrect?
Don't ask me about SmallTalk, since I know almost nothing about it. Googling "smalltalk interfaces" yields a number of promising looking links.