Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with:

Faking Prototypal Inheritance In ColdFusion Components

By Ben Nadel on
Tags: ColdFusion

The other day, I was exploring the way in which ColdFusion serializes component instances. In that exploration, I discovered that ColdFusion uses a combination of information stored in the component meta data, the component's actual methods, and tad bit of voodoo black magic to serialize the CFCs into JSON data strings. What I found very interesting in this approach was that the Meta Data of the component was "sticky." That is, when the meta data of a given component instance was altered, the meta data remained altered for any new instances of the component subsequently created. This shared meta data object got me thinking about Prototypal Inheritance.

 
 
 
 
 
 
 
 
 
 

In Javascript, classes don't extend classes; rather, objects extend other objects known as Prototypes. In this kind of context, if you update the Prototype object, it immediately affects all instances of the sub-classes that extend it. I thought perhaps that the Meta Data object of a ColdFusion component could be used in such a manner; but, as it turns out, the functions outlined in the meta data object are simply a reflection of the compile-time class definition, and not of the runtime functionality (in such a highly dynamic language, that makes sense).

But, while the meta data object itself could not be used as a Prototype, I wondered if it could be leveraged to fake prototype-like behavior. Since the meta data object is shared between all instances of a given class, perhaps it could be used a repository for prototype information. Of course, since there's nothing inherently prototypal about the meta data, I would need to create some sort of conduit to allow shared information to be accessed behind the scenes.

This conduit is the Prototype.cfc base class. But, before we look at how that works, let's take a quick look at how it might be used. To demonstrate this thought experiment, I created a Girl.cfc ColdFusion component:

Girl.cfc

  • <cfcomponent
  • extends="Prototype"
  • output="false"
  • hint="I am a girl object.">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return an initialized object.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • default=""
  • hint="I am the name of the girl."
  • />
  •  
  • <!--- Store the properties. --->
  • <cfset variables.name = arguments.name />
  •  
  • <!--- Return this object reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="sayHello"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I say hello.">
  •  
  • <cfreturn "Hello, my name is #variables.name#." />
  • </cffunction>
  •  
  • </cfcomponent>

As you can see, the Girl.cfc component comes with one method, sayHello().

In our demo code below, we're going to create several instances of the Girl.cfc ColdFusion component and then we're going to update its prototype. This will, effectively, alter the runtime behavior of every existing Girl.cfc instance from a single touch point.

  • <!--- Create out girl objects. --->
  • <cfset girls = [
  • createObject( "component", "Girl" ).init( "Sarah" ),
  • createObject( "component", "Girl" ).init( "Tricia" ),
  • createObject( "component", "Girl" ).init( "Kelly" ),
  • createObject( "component", "Girl" ).init( "Erika" )
  • ] />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <cfoutput>
  •  
  • <!---
  • Loop over all the girls and have them say hello. This is
  • a method that is part of the original Girl class.
  • --->
  • <cfloop
  • index="girl"
  • array="#girls#">
  •  
  • - #girl.sayHello()#<br />
  •  
  • </cfloop>
  •  
  • </cfoutput>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Define a method that we want to add to the shared protoytpe
  • of the girl instances.
  • --->
  • <cffunction
  • name="saySomethingNaughty"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I say something naugghty.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Set up the lighty of naughty actions this girl likes. --->
  • <cfset local.actions = [
  • "spanked",
  • "whipped",
  • "licked",
  • "pinched",
  • "tickled",
  • "teased"
  • ] />
  •  
  • <!--- Randomly pick a naughty action. --->
  • <cfset local.action = local.actions[
  • randRange( 1, arrayLen( local.actions ) )
  • ] />
  •  
  • <!--- Return the naughty something. --->
  • <cfreturn "Hello, my name is #variables.name# and I liked to be #local.action#!" />
  • </cffunction>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <br />
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Add the prototype method to the girl component. Since we have
  • an existing girl object (which extends Prototype), we can use an
  • instance to update the prototype directly. This is going to
  • update the prototype object shared by all instances of the given
  • class.
  •  
  • NOTE: You don't need an actual girl instance to update the class
  • prototype (see next example).
  • --->
  • <cfset girls[ 1 ].addPrototypeMethod(
  • "saySomethingNaughty",
  • saySomethingNaughty
  • ) />
  •  
  •  
  • <cfoutput>
  •  
  • <!---
  • Loop over all the girls and have them say something
  • naughty - keep in mind that this was not a method that
  • was originally part of the class definition.
  • --->
  • <cfloop
  • index="girl"
  • array="#girls#">
  •  
  • - #girl.saySomethingNaughty()#<br />
  •  
  • </cfloop>
  •  
  • </cfoutput>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <br />
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Get access to the prototype for the Girl class. If we didn't
  • have an existing girl instance to work with, we can use the
  • Prototype class as a utility class.
  • --->
  • <cfset girlPrototype = createObject( "component", "Prototype" )
  • .getPrototypeForClass( "Girl" )
  • />
  •  
  • <!---
  • Clear the prototype for the girls. This will remove any
  • methods used in the protoytpe of the Girl class.
  • --->
  • <cfset girlPrototype.clearPrototype() />
  •  
  • <!---
  • Now that we have cleared the prototype, try to call the
  • naughty method on one of the girl instances.
  • --->
  • <cftry>
  • <cfset girls[ 1 ].saySomethingNaughty() />
  •  
  • <cfcatch>
  • <em>"SaySomethingNaughty()" is not supported!</em>
  • </cfcatch>
  • </cftry>

As you can see here, we are creating four Girl.cfc instances and storing them in an array. As a demonstration of the existing Girl.cfc behavior, I am simply invoking the sayHello() method on each girl. Once this core behavior has been demonstrated, I am then defining a new method, saySomethingNaughty(), and adding it to the Girl.cfc prototype. This will make the saySomethingNaughty() method available to each existing Girl.cfc instance. And, in fact, when I run the above code, we get the following output:

- Hello, my name is Sarah.
- Hello, my name is Tricia.
- Hello, my name is Kelly.
- Hello, my name is Erika.

- Hello, my name is Sarah and I liked to be spanked!
- Hello, my name is Tricia and I liked to be spanked!
- Hello, my name is Kelly and I liked to be tickled!
- Hello, my name is Erika and I liked to be licked!

"SaySomethingNaughty()" is not supported!

As you can see, once the ColdFusion component's prototype was updated, the saySomethingNaughty() method became available on each of the existing Girl.cfc instances.

In the last part of the code demo, I simply demonstrate that the Prototype can be altered using a given Girl.cfc instance, or using the Protoytpe base class. Once I have gotten the prototype utility class for the "Girl" class, I am using it to clear the prototype information. Once the prototype information has been cleared, the saySomethingNaughty() method is no longer available.

Now that we've seen how Prototypal inheritance might be used (at least experimentally), let's take a look at how the functionality is faked in ColdFusion. The key to the Prototype.cfc base class is the onMissingMethod() method. All of the prototype functions are stored in the component's meta data; then, when one of the prototype methods is requested, onMissingMethod() re-routes the request to one of the cached meta data methods.

Prototype.cfc

  • <cfcomponent
  • output="false"
  • hint="I provide core prototype functionality through onMissingMethod() and some utility methods.">
  •  
  •  
  • <cffunction
  • name="addPrototypeMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I add the given method to the prototype with the given name.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the method."
  • />
  •  
  • <cfargument
  • name="method"
  • type="any"
  • required="true"
  • hint="I am the method being added to the prototype."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Get the prototype object for this class. --->
  • <cfset local.prototype = this.getPrototype() />
  •  
  • <!--- Add the method to the class prototype. --->
  • <cfset local.prototype[ arguments.name ] = arguments.method />
  •  
  • <!--- Return this object reference for chaining. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="clearPrototype"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I clear the prototype properties.">
  •  
  • <!--- Get the protoytpe object and clear its keys. --->
  • <cfset structClear( this.getPrototype() ) />
  •  
  • <!--- Return this object reference for chaining. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getPrototype"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="I return the prototype being used for this class.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • When getting the prototype object, check to see if
  • we are using this a base class or as a utiltiy class.
  • If this is a utility class, the prototype key will exist.
  • In either case, this is the meta data for this class -
  • NOT of any particular instance. This meta data is shared
  • by all instances of the given class.
  • --->
  • <cfif structKeyExists( this, "__prototype__" )>
  •  
  • <!---
  • This is being used as a utility class, not a base
  • class. Get the component meta data from the given
  • class path.
  • --->
  • <cfset local.metaData = getComponentMetaData(
  • this.__prototype__
  • ) />
  •  
  • <cfelse>
  •  
  • <!---
  • This is being used as a base class. Get the meta
  • data of this particular instance class.
  • --->
  • <cfset local.metaData = getMetaData( this ) />
  •  
  • </cfif>
  •  
  • <!--- Param the prototype property of the meta data. --->
  • <cfparam
  • name="local.metaData.prototype"
  • type="struct"
  • default="#structNew()#"
  • />
  •  
  • <!--- Return the prototype struct. --->
  • <cfreturn local.metaData.prototype />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getPrototypeForClass"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return the prototype being used for the given class.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="className"
  • type="string"
  • required="true"
  • hint="I am the name/path of the class for which we are accessing the prototype."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • Store this prototype class property on this
  • instance - it will be used when accessing the
  • prototype object later.
  • --->
  • <cfset this.__prototype__ = arguments.className />
  •  
  • <!---
  • Return this object reference. Going forward,
  • the programmer can use this instance to update
  • the prototype of the given class.
  • --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="onMissingMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I am used to route methods into the prototype (if they exist).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="missingMethodName"
  • type="string"
  • required="true"
  • hint="I am the name of the missing method."
  • />
  •  
  • <cfargument
  • name="missingMethodArguments"
  • type="any"
  • required="true"
  • hint="I am the argument collection used to try an invoke the missing method."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • We have to var-scope this variable to allow the use
  • of the argumentCollection later on (this is a weird
  • compilation error or something).
  • --->
  • <cfset var prototypeMethod = "" />
  •  
  • <!--- Get the prototype object. --->
  • <cfset local.prototype = this.getPrototype() />
  •  
  • <!---
  • Get the method reference. If the method doesn't exist,
  • just let this aspect throw an error since the code was
  • going to break anyway.
  • --->
  • <cfset prototypeMethod = local.prototype[ arguments.missingMethodName ] />
  •  
  • <!--- Invoke the method. --->
  • <cfset local.result = prototypeMethod(
  • argumentCollection = arguments.missingMethodArguments
  • ) />
  •  
  • <!---
  • Check to see if we have any arguments (a VOID response
  • would have killed the variable).
  • --->
  • <cfif structKeyExists( local, "result" )>
  •  
  • <!--- Return the prototype method result. --->
  • <cfreturn local.result />
  •  
  • <cfelse>
  •  
  • <!--- No prototype response, so just return void. --->
  • <cfreturn />
  •  
  • </cfif>
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="removePrototypeMethod"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I remove the method with the given name from the clas prototype.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="name"
  • type="string"
  • required="true"
  • hint="I am the name of the method being removed."
  • />
  •  
  • <!---
  • Delete the key from the prototype. Since this is
  • passed by reference, we don't have to re-store it.
  • --->
  • <cfset structDelete( this.getPrototype(), arguments.name ) />
  •  
  • <!--- Return this object reference for chaining. --->
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

While this works experimentally, it is definitely a sketchy approach just done for funzies. The meta data of a component is not necessarily persistent. It is treated as a cached Template, from what I can guess; so, there's a chance that it won't cache. Of course, you could use a non-meta-data cache (ex. Application scope) for the functions to overcome this issue. Caching issues aside, however, I can't really think of any practical reason why you would need to change the runtime behavior of all instances of a given component. If a behavior is so necessary, I assume that it would either be made part of the core class definition or injected at instantiation time.




Reader Comments

The whole time I was reading your post i was thinking to myself, why not injet the method at instantiation why got to all this trouble in the first place.

Then i got to your last paragraph and I was like, ahh ok I get it. Experimentation and having the thought "can it be done" leads often to enlightenment in completely unrelated areas (at least that is my belief).

Never the less you have proven that it can be done, and spent, by the looks of things, quite a bit of time and thought into producing an example.

Reply to this Comment

@Gary,

Yeah exactly - sometimes you have to just see if it can be done before you know if it can be useful in any way.

To be honest, I don't even use protoytpal inheritance all that effectively in Javascript either. I use a more standard inheritance approach.

Reply to this Comment

@Ben,

Brilliant! Love it!

I do a lot of API-type stuff and I don't see an immediate use for this there; but, this is definately a technique I'll keep in mind.

Jsut the other day I was wondering if there was a way around the "CF doesn't do that" to accomplish multiple inheritance. Think this technique could be adapted to that purpose?

-Bry

Reply to this Comment

Prototypal Inheritance is something I talk about in my "I bet you didn't know you could do that with ColdFusion" presentation, which I'm giving doing an updated version of at CFUnited 2010. It has some neat uses, particularly in reducing the amount of work per object creation in a large scale application.

http://www.elliottsprehn.com/blog/2010/03/24/using-prototypal-inheritance-in-coldfusion/

On the other side, at the very least it's a neat trick!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.