Note: Coldfusion in and out tag is replaced because of blogging problem)
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:
cfif not structkeyexists(application,instance name)
cfset application.instance name = createobject(”component”,path to component)
/cfifor OnApplicationStart method of your Application.cfc: 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:
cffunction name=”getInstance” access=”public” output=”false”
cfif not isdefined(”application.instance name”)
cfset application.[instance name] = this
/cfif
cfreturn application.[instance name]
/cffunctionRather than hard coding the instance name we can base it on the displayname of the component.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
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):
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
/cfcomponentThe component we want to use as a singleton (dsn.cfc): 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
/cfcomponentUsing the component (in Applicaton.cfm): cfscript
if (not structkeyexists(application,’dsn’)) {
application.dsn = createobject(’component;,’dsn’).getInstance();
application.dsn.setDSN(’mydsn’);
}
/cfscriptor OnApplicationStart method of Application.cfc: cfscript
application.dsn = createobject(’component’,'dsn’).getInstance();
application.dsn.setDSN(’mydsn’);
/cfscriptIn the page: cfquery name=”myquery” datasource=”#applicaton.dsn.getDSN()#”
…
/cfquery Here the example code : singleton.cfc cfcomponent displayname=”singleton”
cffunction name=”init” access=”public” output=”false”
cfset var displayname = getMetaData(this).displayname
cfif not isdefined(”application._singletons”)
cfset application._singletons = structnew()
/cfif
cfif not isdefined(”application._singletons.#displayname#”)
cfset application._singletons[displayname] = this
/cfif
cfreturn application._singletons[displayname]
/cffunction
cffunction name=”remove” access=”public” output=”false”
cfset var displayname = getMetaData(this).displayname
cfif isdefined(”application._singletons.#displayname#”)
cfset structdelete(application._singletons, displayname)
/cfif
/cffunction
/cfcomponentAnd here hows it’s setup in Application.cfm (or .cfc): cfscript
// function to get an instance of a singleton
function getInstance(name) {
if (not isdefined(”application._singletons.#name#”)) {
instance = createobject(”component”,”com.classsoftware.utils.#name#”).init();
}
return application._singletons[name];
}
// function to remove a singleton
function removeInstance(name) {
if (isdefined(”application._singletons.#name#”)) {
application._singletons[name].remove();
}
}
// remove instance if asked
if (isdefined(”url.init”)) {
removeInstance(’dsn’);
}
/cfscriptAnd how it’s used on the page: cfset dsn = getInstance(”dsn”)
cfquery name=”myquery1″ datasource=”#dsn.getDSN()#”
select ….
/cfquery
cfquery name=”myquery2″ datasource=”#dsn.getDSN()#”
select ….
/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: // from this point on when method is called call newmethod instead
cfset instance.method = newmethodMethods can also be removed like so: // 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:
cfcomponent displayname=”singleton”
cffunction name=”init” access=”public” output=”false”
cfscript
var displayname = getMetaData(this).displayname;
this.invalid();
if (not isdefined(”application._singletons”)) {
application._singletons = structnew();
}
if (not isdefined(”application._singletons.#displayname#”)) {
application._singletons[displayname] = this;
}
return application._singletons[displayname];
/cfscript
/cffunction
cffunction name=”remove” access=”public” output=”false”
cfscript
var displayname = getMetaData(this).displayname;
this.invalid();
if (isdefined(”application._singletons.#displayname#”)) {
structdelete(application._singletons, displayname);
}
/cfscript
/cffunction
cffunction name=”invalid” access=”public” output=”false”
cfabort showerror=”Singletons must be created via helper functions not via create object!”
/cffunction
/cfcomponentThe 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: // function to get an instance of a singleton
function getInstance(name) {
if (not isdefined(”application._singletons.#name#”)) {
instance = createobject(”component”,”path..#name#”);
structdelete(instance,”invalid”);
instance.init();
}
return application._singletons[name];
}
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.
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:
cfif not structkeyexists(application,instance name)
cfset application.instance name = createobject(”component”,path to component)
/cfifor OnApplicationStart method of your Application.cfc: 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:
cffunction name=”getInstance” access=”public” output=”false”
cfif not isdefined(”application.instance name”)
cfset application.[instance name] = this
/cfif
cfreturn application.[instance name]
/cffunctionRather than hard coding the instance name we can base it on the displayname of the component.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
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):
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
/cfcomponentThe component we want to use as a singleton (dsn.cfc): 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
/cfcomponentUsing the component (in Applicaton.cfm): cfscript
if (not structkeyexists(application,’dsn’)) {
application.dsn = createobject(’component;,’dsn’).getInstance();
application.dsn.setDSN(’mydsn’);
}
/cfscriptor OnApplicationStart method of Application.cfc: cfscript
application.dsn = createobject(’component’,'dsn’).getInstance();
application.dsn.setDSN(’mydsn’);
/cfscriptIn the page: cfquery name=”myquery” datasource=”#applicaton.dsn.getDSN()#”
…
/cfquery Here the example code : singleton.cfc
cfcomponent displayname=”singleton”
cffunction name=”init” access=”public” output=”false”
cfset var displayname = getMetaData(this).displayname
cfif not isdefined(”application._singletons”)
cfset application._singletons = structnew()
/cfif
cfif not isdefined(”application._singletons.#displayname#”)
cfset application._singletons[displayname] = this
/cfif
cfreturn application._singletons[displayname]
/cffunction
cffunction name=”remove” access=”public” output=”false”
cfset var displayname = getMetaData(this).displayname
cfif isdefined(”application._singletons.#displayname#”)
cfset structdelete(application._singletons, displayname)
/cfif
/cffunction
/cfcomponentAnd here hows it’s setup in Application.cfm (or .cfc): cfscript
// function to get an instance of a singleton
function getInstance(name) {
if (not isdefined(”application._singletons.#name#”)) {
instance = createobject(”component”,”com.classsoftware.utils.#name#”).init();
}
return application._singletons[name];
}
// function to remove a singleton
function removeInstance(name) {
if (isdefined(”application._singletons.#name#”)) {
application._singletons[name].remove();
}
}
// remove instance if asked
if (isdefined(”url.init”)) {
removeInstance(’dsn’);
}
/cfscriptAnd how it’s used on the page: cfset dsn = getInstance(”dsn”)
cfquery name=”myquery1″ datasource=”#dsn.getDSN()#”
select ….
/cfquery
cfquery name=”myquery2″ datasource=”#dsn.getDSN()#”
select ….
/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: // from this point on when method is called call newmethod instead
cfset instance.method = newmethodMethods can also be removed like so: // 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: cfcomponent displayname=”singleton”
cffunction name=”init” access=”public” output=”false”
cfscript
var displayname = getMetaData(this).displayname;
this.invalid();
if (not isdefined(”application._singletons”)) {
application._singletons = structnew();
}
if (not isdefined(”application._singletons.#displayname#”)) {
application._singletons[displayname] = this;
}
return application._singletons[displayname];
/cfscript
/cffunction
cffunction name=”remove” access=”public” output=”false”
cfscript
var displayname = getMetaData(this).displayname;
this.invalid();
if (isdefined(”application._singletons.#displayname#”)) {
structdelete(application._singletons, displayname);
}
/cfscript
/cffunction
cffunction name=”invalid” access=”public” output=”false”
cfabort showerror=”Singletons must be created via helper functions not via create object!”
/cffunction
/cfcomponent
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:
// function to get an instance of a singleton
function getInstance(name) {
if (not isdefined(”application._singletons.#name#”)) {
instance = createobject(”component”,”path..#name#”);
structdelete(instance,”invalid”);
instance.init();
}
return application._singletons[name];
}
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.
No comments:
Post a Comment