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

Post Comment  |  Ask Ben  |  Permalink  |  Print Page




Learning ColdFusion 9 - ColdFusion 9 tutorials, samples, examples, demos

Reader Comments

Oct 11, 2006 at 10:50 AM // reply »
153 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.)


Oct 11, 2006 at 10:56 AM // reply »
6,516 Comments

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.


Oct 11, 2006 at 11:04 AM // reply »
1 Comments

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


Oct 11, 2006 at 11:09 AM // reply »
6,516 Comments

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 :)


Oct 13, 2006 at 8:01 AM // reply »
111 Comments

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!


ike
Aug 8, 2008 at 2:59 PM // reply »
78 Comments

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.


Aug 11, 2008 at 8:04 PM // reply »
6,516 Comments

@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.


Post Comment  |  Ask Ben

Recent Blog Comments
Nov 22, 2009 at 4:30 AM
jQuery Live() Method And Event Bubbling
dasegtezr ... read »
Nov 22, 2009 at 4:03 AM
jQuery Live() Method And Event Bubbling
C_fieri ... read »
Nov 22, 2009 at 1:56 AM
Learning ColdFusion 9: Using CFQuery In CFScript Can Enable SQL Injection Attacks
Why adobe would give you script equivalent of cfquery is beyond me. I love cfquery tag because it helps me wriite clean sql, and get away from the horrible jdbc queries If I wanted to write javali ... read »
Nov 22, 2009 at 1:45 AM
Streaming Text Using ColdFusion's CFContent Tag And The Variable Attribute
The reason you would want to do this is to stream. Ack json/xml files to ria clients I used thus technique before because putting json in response stream causes debugging info to come thru As well a ... read »
Nov 21, 2009 at 6:47 PM
Hal Helms - Real World Object Oriented Development, Sarasota - Day Five
@charlie griefer, Thank you.. ... read »
Nov 21, 2009 at 5:15 PM
Using ColdFusion Structures To Remove Duplicate List Values
@Jose Galdamez, Oh heh yeah I didn't paste the whole code. I should have defined the vars -- my bad. It's fixed thou. Thanks. ... read »
Nov 21, 2009 at 4:49 PM
Styling The ColdFusion 8 WriteToBrowser CFImage Output
Great work yet again Ben! Whilst I didn't use this whole code, I copied some of your regex code for a similar problem with the lack of an alt attribute and unescaped ampersands in CFIMAGE for Railo 3 ... read »
Nov 21, 2009 at 1:13 PM
My First ColdFusion Builder Extension - Encrypting And Decrypting CFM / CFC Files
@Ben, Because I am pedantic, I just want to make sure that everyone knows there is absolutely no encryption going on. There is only encoding and obfuscation. The cfencode tool only obfuscates your C ... read »