Polymorphism

Polymorphism... a big and barely-pronounceable word that has given OO developers a feeling of superiority for decades. I'm about to pop their bubble. "Polymorphism" means simply "things change."

Ok, that might be oversimplified, but it's true. It simply means that something can start life in one form and later change to another. Frogs are polymorphic critters, because they start life as a tadpole and change form drastically over the course of time to become frogs. Insects, with their larva/pupae/adult lifecycle are polymorphic. So you see, the concept of polymorphism isn't really all that difficult.

In object orientation, polymorphism is a word that wraps two other concepts. Those are "Overloading" and "Overriding." These two terms are the basis for some pretty substantial power in OO development. Since CFMX 6.1/7 isn't a complete implementation of OO, we aren't required to learn everything there is to know about them, but a basic understanding goes a long way.

Overriding

You've already seen overrides in this series, actually. Remember our first example, publication.cfc?

<cfcomponent>
<cffunction name="init" access="public" returntype="publication">
<cfargument name="pubName" type="string" required="true" />

<cfset setName(arguments["pubName"])>

<cfreturn this />
</cffunction>


{{...getters and setters...}}

</cfcomponent>

And the second example was slightly different:

<cfcomponent extends="publication">

<cffunction name="init" access="public" returntype="magazine">
<cfargument name="pubName" type="string" required="true" />
<cfargument name="genre" type="string" required="true" />

<cfset super.init(arguments["pubName"])>
<cfset setGenre(arguments["genre"]>

<cfreturn this />
</cffunction>

{{...getters and setters...}}

</cfcomponent>

Now, you'll notice that in between the base class publication.cfc and the extension class, or inheritance class, magazine.cfc, we have two instances of the method named "init." In the case of magazine.cfc, we're adding functionality over and above what's provided in the paren'ts init() method. Since we're effectively replacing that method, we still have to include the cfreturn tag, but we're not prevented from overwriting the original method. Overwrite... or... overRIDE.

That's overriding. In ColdFusionese, that's about all you have to know. You need to make sure that the basis of the previous method is covered, as in our example making sure that the cfreturn passes the instance back to the variable it's to be set in. Other than that, you can have a heyday making base classes and then altering them as the objects become more and more complex... until you're deep in the bowels of your model... ok, let's stop there. You get the idea.

Now, this again is a gotcha. You're thinking OH BOY, I can use this everywhere! No, you can't. You can only use it where the objects in question have an IS-A relationship between classes. In that case, you can use overriding where you need to, and CF even gives you a neat little trick to access the original version of the method, AND the method in your subclass. The "super" scope provides extension CFCs with access to the parent's scope in a situation where a method has been overridden but you need to original version too.

<cfset var myTest = super.init("someString")>

In this case, myTest would be holding what? YES,that's right, an instance of publication.cfc with "someString" in the variables.name element. If for whatever reason you needed to, you could do this:

<cffunction name="testIng" access="private" returntype="string">
<cfargument name="myString" type="string" required="true" />

<cfset var myTest = super.init("myString")>
<cfreturn myTest.getName() />

</cffunction>

It would allow you to gain access to the methods and properties of your parent class without having to do a full createObject call to do it from within an already-instantiated CFC. I've seen it used only rarely, and I do believe it would be fairly obvious when you might need to do so. Whenever you need to reuse the code presented in the parent class would be an appropriate time. Or whenever you need to override a method in a subclass and yet need to have access to the same method in the parent. It takes practice, so write some CFCs and play with them. That will ALWAYS make things make more sense.

Overloading

Some folks complain that overloading isn't available in CFMX. Personally, I think it's a boon. While it does mean we have to take an extra step or two to make things work, there are very easily implemented ways to do anything an overloaded method or constructor could do in Java. You see, "overloading" means that we could have two versions of init() within any given CFC. Why would we want to do that? Well, we could have one that takes an integer argument and returns a populated instance, one that takes a string. That way, if we were dealing with a "person.cfc" class, we could specify myPerson.init("John Smith") or we could specify myPerson.init(3245) (assuming that 3245 is John Smith's ID in the person table of the database). In each case, the init() method would return a valid instance of our person object initialized with John Smith's data... but because of this "constructor overloading" we're able to do it from within one CFC.

Now, let's say John Smith works for a company, and he is both a manager and an employee. Provided that the types of data for either one are the same (telephone number, office address) but the "role_id" value is different, we could use constructor overloading to get an instance of person.cfc for either role in this way:

<cfset myPerson = createObject("component","person").init("John Smith","manager")>

OR...

<cfset myPerson = createObject("component","person").init("John Smith","employee")>

Now, you may be wondering why employee and manager arent subclasses of the person class. Because, the only difference is the value of variables.role. Had the structure of the person object changed, then we would have needed to create a subclass to reflect that structural change. Since, in this case, the ONLY difference is (variables.role = "manager") versus (variables.role = "employee") we didn't need to change any structure, just values.

HOWEVER, the code examples I've just shown you aren't exactly kosher. You see, a person object should only be a person. In the interests of cohesive design, it doesn't need to, and in fact shouldn't, know how to make a person... other than being able to fill it's own pockets with data. Greedy twits...

So at this point, we have to introduce another CF-specific OO method, called the Factory Pattern. It's effectively what we use to replace overloaded constructors in CF... and it's vey simple. You just create a CFC called PersonFactory.cfc, and give it one method called "getObject."

<cfcomponent>

<cffunction name="getObject" returntype="person" access="public">
<cfargument name="objectType" type="string" required="true" />

<cfset var factoryObject = createObject("component",arguments["objectType"])>

<cfreturn factoryObject />
</cffunction>

</cfcomponent>

This is a good time to introduce the concept of "function-local variable declarations." If you look at line 6, which starts with "cfset var" you'll notice "var" (I hope...) and wonder what it is. And now I'll respond to your inquisitive, puzzled look by saying, "Using the var keyword in a function means that the variable in question is set within the function's namespace and only persists from the point of declaration to the completion of the function's execution cycle."

Ok, now in ColdFusionese. "cfset var" tells CFMX that the variable in question should be destroyed as soon as the function is done executing. If we didn't use the var keyword, we would have both set a variable in PersonFactory's instance data that contained an instance of the person object we told it to create, and we would have returned a copy of the same instance to the caller of personFactory.getObject(). That, as a wise CFMX Jedi once told me, will get you banished to programmer hell! It may not seem like a big deal in this particular instance, but in a situation where you've got an instance of a CFC in the application or session scope and its instance data fills up with odd bits of function-leftover variables, it makes a nasty mess. This is, most certainly, a Best Practice concept, and you should ALWAYS use it.

And now back to polymorphism. We can ask our factory object for a person, a manager, an employee, or any other person-derived object. Since manager and employee would be inheritance classes to person, in our method's returntype attribute we can specify person! Inheritance and polymorphism together mean that we can validate and manipulate at the same time. Our Factory object will only be able to return derived classes for the person.cfc class, but that's a good thing. It prevents mistakes, validates our structure, and insures that we're building cohesive classes.

I'll also point out that the only function of the Factory is to create an instance of person or it's descendants, not to run the init method. Why? Because "cohesiveness" means that any given object only does one thing. Factory builds things, that's it. It's up to the caller to run the init method, and it's up to the person instance to fill itself with data. This is where method chaining gets to be a really cool tool:

<cfset myManager = createObject("component","PersonFactory").getObject("manager").init("John Smith")>

Since myManager is in a context to require a populated instance of a manager object, it's OK for it to execute the init() method. It asks CFMX for an object build by PersonFactory of a type called Manager. And CFMX delivers. Then, since we've used method chaining, the PersonFactory goes right on ahead and makes a manager object, and THEN CFMX executes init() against the new manager object... and myManager is John Smith's manager instance, just... that... fast.

So we've covered overrides and overloads, and I'm sure that you're feeling the effects of both after having read all this. It's a big adjustment for CFers who are used to CFQUERY at the top of the page and CFOUTPUT in the middle. But, in the end, this OO-style development paradigm (and more specifically the OO thought process) produces applications that are more stable, easier to maintain, and more reusable than the alternative methods of development.

Go ahead and drink some coffee, give it some though, let it soak in, and then build a CFC with an init, get, and set methods. Make an index.cfm that uses createObject to access it, and play with it. Get data in and out of it. Then make an extension class, or a Factory. Build on the basic pieces, so that when it comes time to build an actual application you're prepared.

I was planning on this to be the last part in the series, but if I get enough comments or emails I could be persuaded go on to other design patterns (like Business Object <-> DAO <-> Database), scope-resident instances, more comprehensive models, beans (Java beans, not musical fruit), etc. Let me know if you like, or if it was helpful and I'll do what I can to keep getting more information like this into the hands of the CFMX development community.

Page Last Updated: 02 Mar 2006, 10:10 AM