ColdFusion Singletons Revisited

My last article on singletons got a few comments on blogs and in email including how it could be improved (thanks Michael) and a few questions. I also omitted the code showing how the singletons were created and called (now added).

So here's the new improved code!


view plain print about
1<cfcomponent displayname="singleton">
3    <cffunction name="init" access="public" output="false">    
4        <cfset var displayname = getMetaData(this).displayname>
6        <cfif not isdefined("application._singletons")>
7            <cfset application._singletons = structnew()>
8        </cfif>
9        <cfif not isdefined("application._singletons.#displayname#")>
10            <cfset application._singletons[displayname] = this>
11        </cfif>    
13        <cfreturn application._singletons[displayname]>
14    </cffunction>
16    <cffunction name="remove" access="public" output="false">
17        <cfset var displayname = getMetaData(this).displayname>    
19        <cfif isdefined("application._singletons.#displayname#")>    
20            <cfset structdelete(application._singletons, displayname)>
21        </cfif>
22    </cffunction>

And here hows it's setup in Application.cfm (or .cfc):

view plain print about
2    // function to get an instance of a singleton
    function getInstance(name) {
4        if (not isdefined("application._singletons.#name#")) {
5            instance = createobject("component","com.classsoftware.utils.#name#").init();
6        }
8        return application._singletons[name];
9    }
11    // function to remove a singleton
    function removeInstance(name) {
13        if (isdefined("application._singletons.#name#")) {
14            application._singletons[name].remove();
15        }
16    }    
18    // remove instance if asked
    if (isdefined("url.init")) {
20        removeInstance('dsn');
21    }

And how it's used on the page:

view plain print about
1<cfset dsn = getInstance("dsn")>
3    <cfquery name="myquery1" datasource="#dsn.getDSN()#">
4        select ....
5    </cfquery>
7    <cfquery name="myquery2" datasource="#dsn.getDSN()#">
8        select ....
9    </cfquery>

The functions getInstance and removeInstance could be placed inside a component that creates/removes singletons (a singleton factory?). However that component itself would need to be a singleton or you'd need to create it (via createobject) on every page. I'll feel it's best just to leave them as user defined functions for simplicity and performance sake.

Anther issue that came up was that you can still use createobject (or <cfinvoke>) to create other instances of the component and there seems no way of stopping this.

Well there's one way I can think of but I'm not sure if I'd actually use it in a production system, but it may be of interest to someone so here's how to do it.

ColdFusion methods can be set at run time, you can add or replace methods by assigning them to new functions like so:

view plain print about
1// from this point on when method is called call newmethod instead
    <cfset instance.method = newmethod>

Methods can also be removed like so:

view plain print about
1// remove method "method" from instance
    <cfset structdelete(instance,"method")>

So you can create a component that has a method that throws an exception (via cfabort) and then have all methods of that component call that method. You can create an instance of the component but if you call any methods you will get an error.

Applying this to our singleton component we get:

view plain print about
1<cfcomponent displayname="singleton">
2    <cffunction name="init" access="public" output="false">
3        <cfscript>
4            var displayname = getMetaData(this).displayname;
6            this.invalid();
8            if (not isdefined("application._singletons")) {
9                application._singletons = structnew();
10            }
11            if (not isdefined("application._singletons.#displayname#")) {
12                application._singletons[displayname] = this;
13            }    
15            return application._singletons[displayname];
17    </cffunction>
19    <cffunction name="remove" access="public" output="false">
20        <cfscript>
21            var displayname = getMetaData(this).displayname;
23            this.invalid();
25            if (isdefined("application._singletons.#displayname#")) {
26                structdelete(application._singletons, displayname);
27            }
29    </cffunction>
31    <cffunction name="invalid" access="public" output="false">
32        <cfabort showerror="Singletons must be created via helper functions not via create object!">
33    </cffunction>    

The this.invalid(); would also needed to be added to all methods of classes than extend singleton.cfc. eg dsn.cfc in the last article.

If you then remove the method that generates the error (via structdelete) before any methods are called then the methods of the instance can be called.

Applying this to our getInstance function we get:

view plain print about
1// function to get an instance of a singleton
    function getInstance(name) {
3        if (not isdefined("application._singletons.#name#")) {
4            instance = createobject("component","<path>..#name#");
5            structdelete(instance,"invalid");
6            instance.init();
7        }
9        return application._singletons[name];
10    }

That way only instances returned from our getInstance function can be used and any other instances created via created object (or other way) will throw an error when a method of that instance is called.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Another issue with the Singleton factory you might find interesting.
# Posted By Michael Long | 4/12/07 9:48 PM
Yes sometimes a lock is required to prevent a race conditions from occurring but in this example it's not required.

A potential race condition exists with the above code, however it would only occur if the application variable doesn't exit and you get perfectly simultaneous requests. (ie once in a blue moon). Even if it does occur and two instances are created the same single instance is returned from the function as the application variable is returned rather than 'this'. ie the code works correctly.

For most requests (where the application variable exists) a race condition doesn't exist so adding a lock may be adversely affect performance by effectively making the code single threaded.
# Posted By Justin Mclean | 4/12/07 10:58 PM
The situation in your case would seem to be the same, just once removed, since both threads could have transitioned into the init method, and hit the 'if (not isdefined("application._singletons.#displayname#"))' test semi-simultaneously. If the first thread then blocked, the other could set "this" and exit, then the first thread could do the same. Dupe.

I also note that you added a "remove" method so the singleton could be recreated. Depending upon circumstances, the number of singletons, and how often that function is called, "blue moons" could occur more often that one might think, especially on a server under heavy load.

All in all, I think it's best to program defensively, and sinply ensure that the bug never has a chance to appear. And since the lock is inside the if test and should only occur once on object creation, the performance aspect is negligible.

I did like deleting the function reference. Neat trick. Adding function references is fun too...
# Posted By Michael Long | 4/12/07 11:40 PM
Ummm... I may be missing someting, but if you delete the object's "invalid" function in the getInstance() factory method and call init(), isn't the init() function going to blow up with a "function not found" error when it hits this.invalid()? I mean, deleting the function doesn't delete the call to the function.

Instead of deleting it wouldn't you need to do something like: instance.invalid=instance.valid; setting the "invalid" function to a safe one so initialization can proceed to completion?
# Posted By Michael Long | 4/13/07 12:09 AM
Understand what your saying but even if it does occur in my code it's not actually a problem as the same instance will be returned. Yes two instances are created but the same one is returned in each case.

If you can do the locking in such as way that it doesn't have an effect on performance then yes it should be added. It may be that later on changes to the code does make the race condition have negative side effects and these issues are very hard to debug/track down.

Glad you liked the removal of methods via structdelete still not 100% sure I'd actually use it in a production system but I'm sure it could be useful under other circumstances.
# Posted By Justin Mclean | 4/13/07 12:29 AM
You would think it would throw an error wouldn't you, however I've tested the code and no error occurs, it's like CF removes the method but not the fact that it existed. If you call getMetaData the method is not listed.

Originally I was checking to see if the method existed before calling it but once I found it wasn't needed I removed the checks to make the code simpler.
# Posted By Justin Mclean | 4/13/07 12:31 AM
I do not think you can &quot;enforce&quot; many of the design patterns in CF (singletons included). Having said that your code is quite elegant but you are not creating a singleton, you are creating a &quot;static&quot; class. Your class is instantiated for ALL USERS and not 1 stances PER USER.
Now you might say ... just scope the class to SESSION... but not really a way to go in my books.
# Posted By AAP | 6/27/08 2:20 AM
Yes without private constructors you can't make a 100% foolproof way of creating a singleton in CF, but you write one that behaves like one as long as you play nicely with it.

Generally I place the instance in the application scope that way it a singleton for the entire application. If it was placed in the session scope it would be a singleton for that users session, which can also be useful in some situations.
# Posted By Justin Mclean | 6/29/08 12:25 PM