A Coldfusion Transient Factory Example

2008 November 07
by Paul Marcotte
When learning how to apply object oriented principles to ColdFusion development, one will assuredly encounter the subject of design patterns. Singleton, Factory, Strategy and Bridge are just some of the patterns that describe solutions to common problems in programming. For this example, I'll demonstrate a variation on the Factory Pattern for transient objects. What is a factory? At it's simplest, a factory encapsulates the creation of objects to provide a consistent api for clients. What is a transient object? A typical OO ColdFusion application will incorporate a combination of Singletons (one instance per application - often instantiated at application startup) and Transients (a per request object that is not maintained in a persistent scope).For instance, when invoking a "save" method on a service, I return a new "result" object. Therefore, each of my services needs to create a new instance of a result object. Rather than place CreateObject("component","path.to.Result") in all services, I use a generic factory pre-configured with object aliases and class paths. I can then use: <cfset result = TransientFactory.create("Result")> But what if the object has setter dependencies or an init() method with constructor args? For any setter dependencies, I incorporate Brian Kotek's beanInjector within the TransientFactory to autowire all matching ColdSpring managed beans. For consturctor args, there are two ways to pass arguments to an init() method. 1. passing a struct of key/value pairs as a second argument to create(). <cfset initArgs = StructNew()>
<cfset initArgs.myprop = "myvalue">
<cfset result = TransientFactory.create("Result",initArgs)>
2. using the virtual method new{objectName}. Transientfactory.newResult(argumentCollection=initArgs). <cfset result = TransientFactory.newResult(myprop="myvalue")> The second method of object creation is made possible by onMissingMethod. Additionally, you can specify the name of a method that the transient factory will invoke after initialization and setter dependency resolution. Here's a simple example for setting up the TransientFactory in ColdSpring. If you use some of Brian Kotek's excellent ColdSpring Bean Utils, you notice a reference to his BeanInjector component. This TransientFactory example is similar to Brian's TDOBeanInjectorObserver component that autowires ColdSpring managed singletons into transfer objects. <beans>
<bean id="beanInjector" class="model.BeanInjector" singleton="true" />

<bean id="TransientFactory" class="model.TransientFactory" singleton="true">
<constructor-arg name="classes">
<map>
<entry key="ServiceResult">
<value>model.util.ServiceResult</value>
</entry>
<entry key="Timer">
<value>model.util.Timer</value>
</entry>
</map>
</constructor-arg>
<constructor-arg name="afterCreateMethod">
<value>setup</value>
</constructor-arg>
<property name="beanInjector">
<ref bean="beanInjector" />
</property>
</bean>
</beans>
Here is the full TransientFactory component code. <cfcomponent displayname="TransientFactory" output="false" hint="I create Transient objects.">

<!--- public --->

<cffunction name="init" access="public" output="false" returntype="any" hint="returns a configured transient factory">
<cfargument name="classes" type="struct" required="false" default="#StructNew()#">
<cfargument name="afterCreateMethod" type="string" required="false" default="">
<cfset variables.classes = arguments.classes>
<cfset variables.afterCreateMethod = arguments.afterCreateMethod>
<cfreturn this />
</cffunction>

<cffunction name="create" access="public" output="false" returntype="any" hint="returns a configured, autowired transient">
<cfargument name="transientName" type="string" required="true">
<cfargument name="initArgs" type="struct" required="false" default="#structNew()#">
<cfset var obj = createObject("component",getClassPath(arguments.transientName))>
<cfif StructKeyExists(obj,"init")>
<cfinvoke component="#obj#" method="init" argumentcollection="#initArgs#" />
</cfif>
<cfset getBeanInjector().autowire(targetComponent=obj, targetComponentTypeName=arguments.transientName) />
<cfif StructKeyExists(obj,variables.afterCreateMethod)>
<cfinvoke component="#obj#" method="#variables.afterCreateMethod#" />
</cfif>
<cfreturn obj>
</cffunction>

<cffunction name="getClasses" access="public" output="false" returntype="struct" hint="returns map of classes that can be created.">
<cfreturn variables.classes>
</cffunction>

<cffunction name="onMissingMethod" access="public" output="false" returntype="any" hint="provides virtual api [new{transientName}] for any registered transient.">
<cfargument name="MissingMethodName" type="string" required="true" />
<cfargument name="MissingMethodArguments" type="struct" required="true" />
<cfif (len(arguments.MissingMethodName) gt 3) and (Left(arguments.MissingMethodName,3) is "new")>
<cfreturn create(Right(arguments.MissingMethodName,Len(arguments.MissingMethodName)-3),arguments.MissingMethodArguments)>
</cfif>
</cffunction>

<!--- private --->

<cffunction name="getClassPath" access="private" output="false" returntype="string" hint="returns the transient class path.">
<cfargument name="transientName" type="string" required="true">
<cfreturn variables.classes[arguments.transientName]>
</cffunction>

<!--- dependencies --->

<cffunction name="setBeanInjector" access="public" returntype="void" output="false">
<cfargument name="BeanInjector" type="any" required="true">
<cfset variables.BeanInjector = arguments.BeanInjector >
</cffunction>

<cffunction name="getBeanInjector" access="public" returntype="any" output="false">
<cfreturn variables.BeanInjector />
</cffunction>

</cfcomponent>
A big thanks to Bob Silverberg for co-authoring this component. You can grab a copy attached as an enclosure.