ColdFusion 8 cfinterface Example - Using An Interface In CF 8
Introduction
ColdFusion 8 added the cfinterface tag, which enables CF developers to use interfaces in their applications. Interfaces specify one or more functions that a ColdFusion component must implement. Coming from being a Java programmer to primarily doing ColdFusion web application development, I missed using interfaces. To help me learn how to use the new cfinterface tag, I implemented the SimUDuck application from chapter 1 of Head First Design Patterns. You can download the code (see the references below) for the application. I'll briefly walk through how cfinterface works.
Please note, don't comment about whether or not interfaces are good to use in ColdFusion. That debate has been done before on other blogs. I regard the cfinterface tag as just another tool CF provides that I may need to use in designing my web applications. Many design patterns use interfaces and having the cfinterface tag available can make implementing those design patterns in CF simpler.
References
- Head First Design Patterns, Eric Freeman and Elisabeth Freeman, O'Reilly
- Adobe CF 8, CFML Reference
- Adobe CF 8, ColdFusion Developer's Guide
- SimUDuck CF 8 Application Code
Discussion
The SimUDuck application is used in the Head First Design Patterns book to illustrate the importance of three design principles:
- Identify the aspects of your application that vary and separate them from what stays the same
- Program to an interface, not an implementation
- Favor composition over inheritance
Briefly, the SimUDuck application includes many different types of ducks. Some ducks fly, some ducks don't fly. New duck types may be introduced in the future and the fly behavior for current and future duck types is likely to change. (Note the SimUDuck application in the Head First book also discusses a quack behavior, but I don't implement that behavior in my CF example code.)
Since the fly behavior can be different for different duck types and since the fly behavior may change, the SimUDuck example application creates a FlyBehavior interface. This interface merely specifies one function (or method) named fly. A function in an interface doesn't specify the details of how the function actually works (there is no function body).
In CF 8, you use the cfinterface tag to create an interface. Below is the code I used to create the FlyBehavior interface. This code is saved in a file named FlyBehavior.cfc
<cfinterface displayName="FlyBehavior">
<cffunction name="fly" displayname="fly"
hint="CFCs that implement the Fly interface must define a fly method"
access="public" output="false" returntype="String" />
</cfinterface>
Note that the fly function has no body. If the function has any arguments you would include those arguments (see CFML reference for an example of an interface where the function has arguments) in the function body.
To use an interface in CF 8, your CFC provides the interface file name as the value of the new implements attribute of the cfcomponent tag. For example, the code below specifies that the FlyWithWings CFC implements the FlyBehavior interface.
<cfcomponent implements="FlyBehavior" displayname="FlyWithWings"
hint="Encapsulates a specific type of fly behavior" output="false">
<cffunction name="fly" displayname="fly"
hint="Defines the fly behavior specified by the FlyBehavior interface"
access="public" output="false" returntype="String">
<cfreturn "I'm flying with wings"/>
</cffunction>
</cfcomponent>
Because the above CFC specifies that it implements the FlyBehavior interface it must define a function named fly. Note that the fly function has a body (it actually performs a specific behavior).
When using interfaces it's important to remember that an interface is a type. So my interface FlyBehavior is a specific type that I can use in my application. For example, in the Duck.cfc I have a setFlyBehavior method that takes an argument of type FlyBehavior.
<cffunction name="setFlyBehavior" access="public" output="false"
returntype="void"
hint="Any CFC that implements the FlyBehavior interface can be used as the argument value">
<cfargument name="flyBehavior" type="FlyBehavior" required="true" />
<cfset variables.flyBehavior = arguments.flyBehavior />
</cffunction>
Any CFC that implements the FlyBehavior interface is also of type FlyBehavior. Since my FlyWithWings implements the FlyBehavior interface, I can use a FlyWithWings object as the argument to the setFlyBehavior function. I also have a FlyRocketPowered CFC that implements the FlyBehavior interface. So I could also use a FlyRocketPowered object as the argument to the setFlyBehavior function.
Interfaces (like inheritance) enable polymorphism. The code below from the Duck CFC (the parent CFC of all specific Duck types like MallardDuck and DecoyDuck) calls the fly function of the variables.flyBehavior object (whose type is FlyBehavior).
<cffunction name="performFly" hint="Calls the fly method of variables.flyBehavior"
displayName="flyBehavior" output="false" access="public"
returntype="String">
<cfreturn #variables.flyBehavior.fly()# />
</cffunction>
The actual code (behavior) executed by the call to the fly function depends on which specific FlyBehavior type object (FlyWithWings or FlyRocketPowered for example) was used to set the value of the flyBehavior object at runtime. If the variables.flyBehavior's value was set using a FlyWithWings object (this is legal since FlyWithWings has type FlyBehavior since it implements the FlyBehavior interface), then the fly function from the FlyWithWings CFC will be executed.
Another advantage of using interfaces and encapsulating behaviors that change, is that we can easily change those behaviors at runtime. In the MiniDuckSimulator.cfm demonstration page, I initially create a DecoyDuck type. The DecoyDuck type's fly behavior is of type FlyNoWay (decoy's don't typically fly). But I can easily change the fly behavior of the DecoyDuck object I created by calling its setFlyBehavior method and passing it a new FlyBehavior type object (remember any CFC that implements the FlyBehavior interface will be of type FlyBehavior). So I can create an object of type FlyRocketPowered and pass that object to the setFlyBehavior function of my DecoyDuck and now my DecoyDuck is "flying with a rocket."
Where To Go Next
Download the SimUDuck CF application code to view an example of how to use the CF 8 cfinterface tag. The MiniDuckSimulator.cfm page shows how I used the CFCs and changed an object's fly behavior at runtime.
Read through the explanation of the cfinterface tag in the CFML Reference. Unfortunately, there aren't yet any good examples or explanations about the cfinterface tag and using interfaces in the ColdFusion 8 Developer's Guide.
Lastly, if you're trying to wrap your head around object-oriented programming, I recommend two books: Head First Object-Oriented Analysis and Design and Head First Design Patterns. The code examples in both books are written in Java, but they can easily be converted to CF code examples. The new CF 8 cfinterface tag makes implementing these examples and design patterns in ColdFusion even easier!
If we built the SimUDuck application in CF 7 without using interfaces we would need to create classes that encapsulate the specific fly behavior (for example FlyWithWings and FlyRocketPowered CFCs). In these classes we would define the fly function. Each of these classes could inherit from a general FlyBehavior class so that they would have that type and then could be used anywhere a FlyBehavior object is required (for example in the setFlyBehavior function).
However, since the FlyWithWings CFC doesn’t implement the FlyBehavior interface, the designer of that class is free to call the fly function anything he wants (say flyDuck) instead of fly. When we check our CFC by viewing it in the browser, everything will look fine. But when we try to run our application and our MallardDuck object calls the fly function of its variables.flyBehavior object, our application will fail since FlyWithWings doesn’t have a fly function (the designer named it flyDuck).
In the SimUDuck application created using CF 8’s cfinterface tag the person designing the FlyWithWings class must create and fully define a function named fly. If he doesn’t, he’ll see an error when viewing the CFC in the browser.
So using cfinterface helps teams developing large applications enforce how functions are named and also a function’s return type.
Another reason you may want to use interfaces in CF is when your CFC already extends a parent CFC. If you want to give the child CFC another type to enable polymorphism, you can add an interface to your application and have your CFC implement that interface. Now your CFC has three types (its own, its parent, and the type of the interface it implements). So an object created using that CFC can be used anywhere one of those three types are required. Additionally, though your CFC can only extend one parent CFC, your CFC can implement more than one interface. This makes your CFC more flexible.
Some of the benefits of using interfaces were already available in CF 7 since CF is not a strictly typed language (the type for an argument can be “any”) and it is possible to add functions to a CFC at runtime.
However, I believe the cfinterface tag makes it easier for developers to design larger, more complex applications that heavily use polymorphism.
I looked up the definition of polymorphism and it seems that if I have an existing cfc that lets programmers send different data types to it then it supports, by definition, polymorphism.
In the SimUDuck application there is a FlyBehavior type. Our Duck parent CFC has a setFlyBehavior that takes an argument of type FlyBehavior. Any CFC that implements the FlyBehavior interface will have type FlyBehavior and therefore can be used as the value for the argument. That is one aspect of polymorphism. Since my FlyWithWings CFC implements the FlyBehavior interface, I can use a FlyWithWings object as the argument to the setFlyBehavior method. The FlyWithWings CFC has multiple types and can assume either of its types whenever necessary.
Another aspect of polymorphism is illustrated by the Duck class performFly function. This function calls the variables.flyBehavior.fly() function. Every object of type FlyBehavior must have a fly function since the FlyBehavior interface required that function. However, each CFC that implements the FlyBehavior interface is free to define its own logic in the fly function (though it must return a String). Whenever our application calls the performFly function of our Duck type object (for example we can create a MallardDuck object and call its performFly function), the actual code executed will be determined at runtime. If our Duck object’s variables.flyBehavior was set to equal an object created using the FlyWithWings CFC then the fly function defined inside the FlyWithWings CFC will be used. But if our Duck object’s variables.flyBehavior was set to equal an object created using the FlyRocketPowered CFC then the fly function defined inside the FlyRocketPowered CFC will be used.
Polymorphism is a powerful tenet of object-oriented programming. Using interfaces in our applications helps us create applications that can take advantage of polymorphism. In the future if we need a new fly behavior (for example some ducks may need a fly after being shot behavior), we just need to create a new CFC that implements the FlyBehavior interface and defines this new fly function. We can then simply call the Duck object’s setFlyBehavior method and pass it an object created using the new FlyBehavior CFC. All the rest of our code (the various Duck type CFCs) doesn’t need to be changed and because of polymorphism our performFly function in the Duck classes will work just fine with the new FlyBehavior type.
show up? Do have to <cfoutput> it from #varables. or does it just flash on the screen or what. I do not see my <cfreturns returning nothing?!?!
Whatever String (for example "I'm flying with wings") is returned by the fly method called when executing the code #variables.flyBehavior.fly()# is the just further returned by the Duck CFC's performFly function (which contains the above cfreturn statement).
If you run MiniDuckSimulator.cfm (see the code download) you'll see the String returned by the performFly function displayed on the web page.
This line in MiniDuckSimulator.cfm:
<p>#aMallardDuck.performFly()#</p>
calls the performFly function and the String ("I'm flying with wings") that is returned by the performFly function is displayed on the webpage inside paragragh tags.