Pages

Thursday, August 12, 2010

Intro To the CFInterface Tag With A Real World Example

One of the most confusing things I encountered while learning OO concepts was the concept of interfaces.  Definitions of interfaces was were abundant but I couldn't find much information on how they were used in an actual application.   This post will focus on a real world example of using an interface in an application and hopefully help clarify how they can be used to create more flexible objects. Also, please feel free to leave me a comment and tell me where I'm way off here, I'm always learning and willing to listen to any good constructive criticism.  

At a very high level interfaces are types, just like components are types and can be passed around similar to components.  The difference between an interface and a component is that an interface has no implementation code (code that actually does something) inside of the function definition.   All an interface does is define functions and their signatures.  A function's signature is its name plus unique combination of arguments.   Lets take a look at  an example of an interface that defines a "Document" in a system that I am currently working on.

interface  hint="I represent either a binary or xml document"
{
     public any function GetDocumentData()
     hint="I send back document data" output="false";

}

Pretty simple looking, right?  The main thing to note here is that there is no implementation code, the function is just a skeleton.  This interface is a contract for any component that implements it saying that the component must have a method called GetDocumentData that returns the type of "any".  Its up to the implementing component to actually do stuff within the GetDocumentData function.

For the application I'm currently working on a Document object can represent either a Binary document such as a Word or Excel document or an XML document.   Since the handling of binary data is different than the handling of XML data I needed to create seprate components to handle each type of document. Here is the code for the  BinaryDocument Component:

component displayname="BinaryDocument" implements="IDocument" hint="I represent a binary document (word, excel, etc)" output="false"
{
     variables.documentData = ToBinary('');

     public void function SetDocumentData(binary documentData){
          variables.documentData = arguments.documentData;
     }

     public any function GetDocumentData()
     hint="I send back document data" output=false
     {
          return variables.documentData;
     }
}

You can see that the GetDocumentData function now returns variables.documentData which defaults to an empty Binary object and is set by the SetDocumentData function. The XMLDocument component is very similar, except the document data is stored as a string:

component displayname="XMLDocument"  implements="IDocument" hint="I represent a XML document (word, excel, etc)" output="false"
{
 variables.documentData = "";

 public void function SetDocumentData(string documentData){
  variables.documentData = arguments.documentData;
 }
 
 public any function GetDocumentData()
 hint="I send back document data" output=false
 {
  return IsXml(variables.documentData)?XMLParse(variables.documentData):false;
 }
}

Now this is were interfaces become useful.  My system also includes an Attachment component that can represent either a binary or XML document. The Attachment component has a Document property which is set using the SetDocument function.  Whats important to see here is that the argument type that the SetDocument function expects is IDocument.  This means that any component that implements the IDocument interface can be passed into this function.  Since we know that the IDocument interface defines a function called GetDocumentData we can call that function on the Document property to return the data from any type of document.   The code for the Attachment object is shown below (I've removed all the other properties from this function to keep things terse):

component displayname="Attachment" hint="I represent an attachment" output="false"
{
 property name="variables.Document" type="IDocument" setter="true";

 public IDocument function getDocument()
 {
  return Document;
 }

 public void function SetDocument( required IDocument Document )
 {
  variables.Document = arguments.Document;
 }

 public any function GetDocumentData()
  description="I return a documents data as defined by the Document object." output="false"
 {
  return variables.Document.GetDocumentData();
 }

}

The following code is shows how all of these components are put together to output different types of data:


 //create a new binary document
 BinaryDocument = new BinaryDocument();
 BinaryDocument.SetDocumentData(toBinary(''));
 
 //create a new XML document
 XMLDocument = new XMLDocument();
 XMLDocument.SetDocumentData('XML is it your friend or foe?');

 Attachment = new Attachment();
 Attachment.setDocument(XMLDocument);
 
 WriteDump(Attachment.GetDocumentData());
 
 Attachment.setDocument(BinaryDocument);
 
 WriteDump(Attachment.GetDocumentData());


This is what the preceding code outputs:


For additional reading on Interfaces check out Sun's Java Tutorials here: http://download.oracle.com/javase/tutorial/java/concepts/interface.html.  I'd also suggest you read http://www.artima.com/lejava/articles/designprinciples.html where Erich Gamma talks about why programing using interfaces is preferred over inheritance here .