Singletons are perhaps one of the most simple Design Patterns. For those who don't know sigletons are a class that can only have one instance. They can be thought of as a glorified global variable - but are a lot more useful.
Most ColdFusion classes, or rather instances of CF components, can be turned in a singleton by placing the following code in your Application.cfm:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfif not structkeyexists(application,<instance name>)>
<cfset application.<instance name> = createobject("component",<path to component>)>
</cfif>
1<cfif not structkeyexists(application,<instance name>)>
2 <cfset application.<instance name> = createobject("component",<path to component>)>
3 </cfif>
or OnApplicationStart method of your Application.cfc:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfset application.<instance name> = createobject("component",<path to component>)>
1<cfset application.<instance name> = createobject("component",<path to component>)>
The above code places an instance of the component in the application scope and you can then access the properties and methods of the component via the application variable.
Singletons can also be placed in other ColdFusion scopes such as the server or session scopes or even the request scope. Which scope you choose depends on what your code does.
Another way to create a singleton is to add an getInstance method to your component and use that to return the instance.
Like so:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cffunction name="getInstance" access="public" output="false">
<cfif not isdefined("application.<instance name>")>
<cfset application.[<instance name>] = this>
</cfif>
<cfreturn application.[<instance name>]>
</cffunction>
1<cffunction name="getInstance" access="public" output="false">
2
3 <cfif not isdefined("application.<instance name>")>
4 <cfset application.[<instance name>] = this>
5 </cfif>
6
7 <cfreturn application.[<instance name>]>
8 </cffunction>
Rather than hard coding the instance name we can base it on the displayname of the component.
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cffunction name="getInstance" access="public" output="false">
<cfset var displayname = getMetaData(this).displayname>
<cfif not isdefined("application.#displayname#")>
<cfset application.[displayname] = this>
</cfif>
<cfreturn application.[displayname]>
</cffunction>
1<cffunction name="getInstance" access="public" output="false">
2 <cfset var displayname = getMetaData(this).displayname>
3
4 <cfif not isdefined("application.#displayname#")>
5 <cfset application.[displayname] = this>
6 </cfif>
7
8 <cfreturn application.[displayname]>
9 </cffunction>
While this is an improvement on the original code this method would need to be added to all components you wanted to turn into a singleton. A better solution is to create a singleton component and in a component you need to turn into a singleton extend from the singleton component.
The singleton component (singleton.cfc):
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfcomponent displayname="singleton">
<cffunction name="getInstance" access="public" output="false">
<cfset var displayname = getMetaData(this).displayname>
<cfif not isdefined("application.#displayname#")>
<cfset application[displayname] = this>
</cfif>
<cfreturn application[displayname]>
</cffunction>
</cfcomponent>
1<cfcomponent displayname="singleton">
2
3 <cffunction name="getInstance" access="public" output="false">
4 <cfset var displayname = getMetaData(this).displayname>
5
6 <cfif not isdefined("application.#displayname#")>
7 <cfset application[displayname] = this>
8 </cfif>
9
10 <cfreturn application[displayname]>
11 </cffunction>
12
13</cfcomponent>
The component we want to use as a singleton (dsn.cfc):
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfcomponent displayname="DSN" extends="singleton">
<cfset variables.DNS = "">
<cffunction name="getDSN" access="public" returntype="string" output="false">
<cfreturn variables.DSN>
</cffunction>
<cffunction name="setDSN" access="public" output="false">
<cfargument name="DSN" type="string" required="yes">
<cfset variables.DSN = arguments.DSN>
</cffunction>
</cfcomponent>
1<cfcomponent displayname="DSN" extends="singleton">
2 <cfset variables.DNS = "">
3
4 <cffunction name="getDSN" access="public" returntype="string" output="false">
5 <cfreturn variables.DSN>
6 </cffunction>
7
8 <cffunction name="setDSN" access="public" output="false">
9 <cfargument name="DSN" type="string" required="yes">
10 <cfset variables.DSN = arguments.DSN>
11 </cffunction>
12
13</cfcomponent>
Using the component (in Applicaton.cfm):
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfscript>
if (not structkeyexists(application,'dsn')) {
application.dsn = createobject('component;,'dsn').getInstance();
application.dsn.setDSN('mydsn');
}
</cfscript>
1<cfscript>
2 if (not structkeyexists(application,'dsn')) {
3 application.dsn = createobject('component;,'dsn').getInstance();
4 application.dsn.setDSN('mydsn');
5 }
6 </cfscript>
or OnApplicationStart method of Application.cfc:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfscript>
application.dsn = createobject('component','dsn').getInstance();
application.dsn.setDSN('mydsn');
</cfscript>
1<cfscript>
2 application.dsn = createobject('component','dsn').getInstance();
3 application.dsn.setDSN('mydsn');
4 </cfscript>
In the page:
ColdFISH is developed by Jason Delmore. Source code and license information available at coldfish.riaforge.org
<cfquery name="myquery" datasource="#applicaton.dsn.getDSN()#">
...
</cfquery>
1<cfquery name="myquery" datasource="#applicaton.dsn.getDSN()#">
2 ...
3 </cfquery>
And while the sample code isn't bad, you'd have been better off creating a member variable within the factory itself to hold the instance you're creating. This is a good idea for two reasons:
1) One is to minimize side effects and avoid polluting the global application name space.
Someone maintaining the code may decide to create his own "application.dsn" variable, not realizing that deep within the bowels of your factory that application variable name is eventually going to get stepped on.
2) It makes it easier to "reset" your application when you make a code change.
The later point needs elaboration. Let's say your singleton facotry has grown to instantiate five different cached objects. With the method you've shown, that will also create five external application variables to hold them.
Now say you want to create a new kind of "dsn" object, that also has usernames and passwords. With your method you have to reinstantiate the factory, and also know that you need to zap the external application variable, since as far as the new factory knows application.dsn already exists.
If, however, the dsn object was stored within the factory in a member variable, reinstantiating the factory "resets" all of its internally cached objects as well, and as such new ones will be automatically created the next time they're requested.
In fact, if I were the aforementioned developer sent in to maintain your code, that's the behaviour I'd expect, in that once I recreate a factory it's going to return the proper results (e.g. new versions of new objects). By the way, the function name or hint attribute should always indicate that caching is occurring. (factory.getCachedDSN() or hint="Returns a cached DSN object, creating a new one if needed.")
All in all, manipulating external scopes breaks the "black box"; paradigm of an object being self-contained code and data, and generally should be avoided unless absolutely neccesary.
Good article otherwise.
Thanks for the comments.
Not sure if it could be classed as a true factory even if it acts as one as it's only creating a single instance of one class, factories generally create multiple instances of multiple classes.
What I normally do is make a structure in the applications cope and all singletons are placed in that, that minimizes the issues of collisions. I omitted this from above code for simplicity sake.
The reason I have the method create the object in the application scope rather than doing it externally to the object is that a) it avoids coding errors and b) all singletons of this nature need to be in the application scope. I agree with you that generally it should be avoided and if this was Java or C++ there would be no need to do this. Possibly a more flexibly way would be to create an init method (as ColdFusion has no constructors) and pass the name of the variable you want to store the singleton in?
I also usually create a removeInstance method that does a structdelete on the instance variable for use for resetting. If there's enough interest I'll post the full code.
Justin
And I hope you don't mind, but I sort of took the singletons idea and ran with it. (http://cfinternals.typepad.com/blog/2007/04/when_i...)
http://blog.classsoftware.com/index.cfm/2007/4/10/...