Swapping ColdFusion Component Methods At Run Time

Posted October 11, 2006 at 9:28 AM

Tags: ColdFusion

I was in the shower this morning, thinking about Peter Bell and his LightWire code. LightWire (to dumb it down a lot) automates the passing in of dependent object during object creation / initialization. This got me thinking: we can store variables at run time but what about methods? Methods are just variables right??? Traditionally I think of methods as compile time objects. I have done things like that in Javascript where you can add functions to an object at any time because it's a really lose, awesome language, but what about ColdFusion? When it comes to methods in Components, it's not just about the method code... it's about references (ex. THIS, VARIABLES, SUPER). How does that translate at run time?

To test this, I have set up two components, Boy and Girl:

Girl.cfc

 Launch code in new window » Download code as text file »

  • <cfcomponent
  • displayname="Girl"
  • output="false"
  • hint="I am a chicka.">
  •  
  • <cffunction name="Init" access="public" returntype="Girl" output="false"
  • hint="Returns an initialized Girl instance.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Name" type="string" required="yes" />
  •  
  • <!--- Save name value. --->
  • <cfset THIS.Name = ARGUMENTS.Name />
  • <cfset VARIABLES.Name = ARGUMENTS.Name />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction name="ActInappropriately" access="public" returntype="string" output="false"
  • hint="I am about to do something inappropriate.">
  •  
  • <!--- Do something crazy. --->
  • <cfreturn (
  • UCase( VARIABLES.Name ) & ": I am " & THIS.Name & " and I just flashed my boobies :)"
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

Boy.cfc

 Launch code in new window » Download code as text file »

  • <cfcomponent
  • displayname="Boy"
  • output="false"
  • hint="I am a dude.">
  •  
  • <cffunction name="Init" access="public" returntype="Boy" output="false"
  • hint="Returns an initialized Boy instance.">
  •  
  • <!--- Define arguments. --->
  • <cfargument name="Name" type="string" required="yes" />
  •  
  • <!--- Save name value. --->
  • <cfset THIS.Name = ARGUMENTS.Name />
  • <cfset VARIABLES.Name = ARGUMENTS.Name />
  •  
  • <!--- Return This reference. --->
  • <cfreturn THIS />
  • </cffunction>
  •  
  •  
  • <cffunction name="ActInappropriately" access="public" returntype="string" output="false"
  • hint="I am about to do something inappropriate.">
  •  
  • <!--- Do something crazy. --->
  • <cfreturn (
  • UCase( VARIABLES.Name ) & ": I am " & THIS.Name & " and I just pooped myself :("
  • ) />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, both these objects have a way to act inappropriately (oh you guys!).

Let's start off by instantiating each of these objects and then getting them to act inappropriately (just to make sure everything is up and running):

 Launch code in new window » Download code as text file »

  • <cfset Girl = CreateObject( "component", "Girl" ).Init(
  • Name = "Sarah"
  • ) />
  •  
  • <cfset Boy = CreateObject( "component", "Boy" ).Init(
  • Name = "Jeff"
  • ) />
  •  
  • <!--- Do something crazy. --->
  • #Girl.ActInappropriately()#<br />
  • <br />
  •  
  • <!--- Do something crazy. --->
  • #Boy.ActInappropriately()#<br />

This gives us the output:

SARAH: I am Sarah and I just flashed my boobies :)

JEFF: I am Jeff and I just pooped myself :(

Nice, working well. Let's just take a look at the functions for a second. Both of them refer to the THIS scope as well as the VARIABLES scope. They have the same simple value - Name was stored in both scopes. Let's see what happens when we swap the methods at run time (for the rest of these tests, assume that the Boy and Girl objects have been instantiated already):

 Launch code in new window » Download code as text file »

  • <!--- Move the boy function over to the girl. --->
  • <cfset Girl.ActInappropriately = Boy.ActInappropriately />
  •  
  • <!--- Do something crazy. --->
  • #Girl.ActInappropriately()#<br />
  • <br />
  •  
  • <!--- Do something crazy. --->
  • #Boy.ActInappropriately()#<br />

This gives us the output:

SARAH: I am Sarah and I just pooped myself :(

JEFF: I am Jeff and I just pooped myself :(

As you can see, the functionality of the Boy's method was copied over to the Girl's method, however, both the THIS and the VARIABLES scope pointers translated appropriately to the new component. That's pretty cool!

Ok, that's moving a method from one component to another... but what happens if I want to create an entirely new method and store it in a component. To test this, I created this non-component-contained method, RunNaked:

 Launch code in new window » Download code as text file »

  • <cffunction
  • name="RunNaked"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I am about to run around naked.">
  •  
  • <!--- Do something crazy. --->
  • <cfreturn (
  • "I am " & THIS.Name &
  • " and I just ran naked around the office :D"
  • ) />
  • </cffunction>

This method refers to the THIS scope. Outside of a component, this makes no sense. In fact, if I try to call this method as is:

 Launch code in new window » Download code as text file »

  • <!--- Run naked. --->
  • #RunNaked()#

... it throws the following error:

Element NAME is undefined in THIS.

Ok, but that's not what I want to test. Let's store this method pointer into the girl's ActInappropriately method (if only this were real, right!):

 Launch code in new window » Download code as text file »

  • <!--- Store a completely new function pointer. --->
  • <cfset Girl.ActInappropriately = RunNaked />
  •  
  • <!--- Do something crazy. --->
  • #Girl.ActInappropriately()#<br />
  • <br />
  •  
  • <!--- Do something crazy. --->
  • #Boy.ActInappropriately()#<br />

This gives us the output:

I am Sarah and I just ran naked around the office :D

JEFF: I am Jeff and I just pooped myself :(

Very cool! Even though the new method has no idea about what a component even was (as it was defined outside of any components), when moved over to the Girl instance, the THIS scope hooked up quite nicely.

This has been a cool little test don't you think? I have not fully looked into it, but I think this is the idea behind ColdFusion Mixins. I am not sure what I would ever use this for, but it might come in handy one day to be able to create an Empty ColdFusion Component and then dynamically give it functionality. Who knows!

Download Code Snippet ZIP File

Comments (7)  |  Post Comment  |  Ask Ben  |  Permalink  |  Print Page




Keep your Web site content fresh and your overhead costs low with Savvy Content Manager

Reader Comments

Another thing you can do is dynamically delete methods.

Say you have a PoleDancer class that implements a Dance() method and a Stripper class that implements a Strip() method as well as the Dance() method. Say you then have a function that operates one way if you pass it a PoleDancer and another if you pass it a Stripper (duck typing). You can call StructDelete(myStripper,"Strip") to remove that method from the Stripper, and thus the function will think that the object is a PoleDancer, not a Stripper. If you wanted to be extra sneaky:

myStripper.OldStrip = myStripper.Strip;
StructDelete(myStripper,"Strip");
PutOnAShow(myStripper); // Dance, dance, Dance()
myStripper.Strip = myStripper.OldStrip;
StructDelete(myStripper,"OldStrip");

(There are other ways to do is-a in CF, but the fastest, easiest, and most often implemented is just a StructKeyExists(obj,"Strip"), which makes this work.)

Posted by Rick O on Oct 11, 2006 at 10:50 AM


Rick,

Well played (and thanks for getting into the spirit of the posting ;)). Now that you mention the deleting, I think I read something in the last ColdFusion Quarterly, by Ray Camden, that dynamically deletes the OnRequest() method from the Application.cfc if there is .CFC or WSDL in the URL. Can't find a link to it though.

But thanks, I totally forgot about deleting stuff. And the storing the old reference is very cool.

Posted by Ben Nadel on Oct 11, 2006 at 10:56 AM


Very cool.

Actually, this is also very practical. It looks a whole lot like "mixins" in ruby - a way of implementing multiple inheritance. So you can configure an object's behavior dynamically (at runtime), giving it whatever functions it needs to do the job at hand.

Hal Helms writes about this quite a bit:

http://www.halhelms.com/index.cfm?fuseaction=newsletters.show&issue=jan2006

Posted by Mark DeMoss on Oct 11, 2006 at 11:04 AM


Mark,

Great link! I don't know much about Ruby, but I think this is also the way things like Prototype and JQuery extend Javascript objects.

I am looking forward to working this in somewhere :)

Posted by Ben Nadel on Oct 11, 2006 at 11:09 AM


Hi Ben,

Nice post! The first piece is what Hal/Sean (in the CF world) describe as object based mixins. You can even cfinclude methods into a template on component instantiation for class based mixins. I've never seen just having the function sitting there all alone and adding it to a class, but it is cool to see that works too!

Posted by Peter Bell on Oct 13, 2006 at 8:01 AM


I actually just quite recently updated the function libraries in the onTap framework to load methods on demand using onMissingMethod in CF8. (And am just now reading this blog entry 2yrs late). :) The libraries themselves were originally created on CF5 before there even were CFCs and until just this latest version (3.2) they were always stored in structures instead of CFCs. The dream had always been to have a collection of functions that could understand their own dependencies and be loaded as-needed instead of being required to aggressively load when the application starts. (DLL wouldn't be a terrible analogy.) But making that actually happen proved to be too difficult to manage prior to CF8 and onMissingMethod. Now they finally do, which eliminated a nice big chunk of delay when the application loads.

Posted by ike on Aug 8, 2008 at 2:59 PM


@ike,

Glad you got it working. I am just starting to really appreciate the OnMissingMethod() functionality in ColdFusion 8. In OOPhoto, its really allowing me to rock some very dynamic and "smart" behaviors on my objects.

Posted by Ben Nadel on Aug 11, 2008 at 8:04 PM


Post Comment  |  Ask Ben


Home   |   Web Log   |   ColdFusion   |   Projects   |   Resume   |   Job Form   |   Search   |   Contact
Epicenter Consulting - Custom Software Solutions for Business Evolution HostMySite.com - The Leader In ColdFusion Hosting