Intro to Object Factories with slides and code
I presented at the Frameworks Conference a session called Intro to Object Factories and I forgot/did not have time to post the slides and code. For those of you new to this concept, an object factory is nothing but a component whose sole job is to create components. An object factory is quite simple and small. Its main role is to collect the information necessary to create an instance of the intended object's class and then to invoke that class's constructor.
In the conference I talked and explained all the boring theory behind it, but what seemed to be the most effective part of the session is the one where I showed code, and I followed step by step on how to create and use one.... so let's jump straight to it.
To illustrate my point, I chose to use Ray Camden's Galleon forums. I've used this forums and I like the code, however, Ray could have done a better job wiring the component dependencies. As a side note, I am actually glad to see that I made a difference, and Ray is in the process of adapting some factory patterns and perhaps even add them to Galleons in the future.
So back to the code, with the download, you will find four copies of the Galleon forums, all working, but all using different approaches when it comes to creating, initializing, and wiring the components (Download the slides and source code)
For lack of a better name, I named the folders forums, forums2, forums3, and forums4.
Forums: Original Galleon Forums
Forums2: Small Object Factory with Dependency Injection
Forums3: Small Object Factory without Dependency Injection
Forums4: ColdSpring
Let's start with Forums3. If you look at Application.cfm and compare it to Forums, you would note that I initialized an application.factory, which subsequently I use to create the rest of the objects that reside in the application scope. Note that I did not want to modify any of the Galleon's core, controllers, views, so I left all the code intact with the exception of the cfcs. In a different scenario, you may not even have to scope all these components in the application, but instead just get them from the factory when needed.
<cfset application.factory = createObject("component","cfcs.objectFactory").init()>
<!--- Get main settings --->
<cfset application.settings = application.factory.getInstance('galleonSettings').getSettings()>
<!--- get user CFC --->
<cfset application.user = application.factory.getInstance('user')>
<!--- get utils CFC --->
<cfset application.utils = application.factory.getInstance('utils')>
<!--- get conference CFC --->
<cfset application.conference = application.factory.getInstance('conference')>
<!--- get forum CFC --->
<cfset application.forum = application.factory.getInstance('forum')>
<!--- get thread CFC --->
<cfset application.thread = application.factory.getInstance('thread')>
<!--- get message CFC --->
<cfset application.message = application.factory.getInstance('message')>
<!--- get rank CFC --->
<cfset application.rank = application.factory.getInstance('rank')>
The factory looks as follows
Name : objectFactory.cfc
Author : Rob Gonda
Created : August 25, 2006
Last Updated : August 25, 2006
History :
Purpose : Simple Object Factory / Service Locator
--->
<cfcomponent displayname="objectFactory" hint="I am a simple object factory">
<!---
function init
in:
out: this
notes: usually initialized in application
--->
<cffunction name="init" access="public" output="No" returntype="objectFactory">
<cfscript>
// persistance of objects
variables.com = structNew();
</cfscript>
<cfreturn this />
</cffunction>
<!---
function getObject
in: name of object
out: object
notes:
--->
<cffunction name="createObj" access="public" output="No" returntype="any">
<cfargument name="objName" required="Yes" />
<cfscript>
switch(arguments.objName) {
case "conference":
return createObject('component','conference').init(this);
break;
case "forum":
return createObject('component','forum').init(this);
break;
case "galleonSettings":
return createObject('component','galleon');
break;
case "message":
return createObject('component','message').init(this);
break;
case "rank":
return createObject('component','rank').init(this);
break;
case "thread":
return createObject('component','thread').init(this);
break;
case "user":
return createObject('component','user').init(this);
break;
case "utils":
return createObject('component','utils');
break;
}
</cfscript>
</cffunction>
<!---
function getInstance
in: name of object
out: object
notes: create a persistant object if doen not previously exists
--->
<cffunction name="getInstance" access="public" output="No" returntype="any">
<cfargument name="objName" required="Yes" />
<cfscript>
if ( not StructKeyExists(variables.com, arguments.objName) ){
variables.com[arguments.objName] = createObj(arguments.objName);
}
return variables.com[arguments.objName];
</cfscript>
</cffunction>
<cffunction name="removeInstance" access="public" output="No" returntype="void">
<cfargument name="objName" required="Yes" />
<cfscript>
if ( StructKeyExists(variables.com, arguments.objName) ){
structDelete(variables.com, arguments.objName);
}
</cfscript>
</cffunction>
</cfcomponent>
It has a few basic functions:
init: initializes the factory... does nothing except creating a holder for the components and returning itself.
createObj: has a big switch/case that knows how to initialize all the components, depending on the object name passed to the factory. This method will not store/scope the component, but simple return an initialized instance. Note that the only argument passed to the components is 'this', which is a pointer back to itself. All components will be 'factory-aware', which means that they know about the factory, and if they need any other component they should just ask the factory to provide them with one.
getInstance: this method simply checks if a component has been initialized; if it has, it returns its instance, and if it has not, it would create a new instance, scope it, and return it. This method ensures that every time you request an instance of an object, it will return the same one... this is know as a singleton pattern. (in strict design patterns, there would be more to this, but let's just run with the very basic concept).
removeInstance: I created this method for commodity, so I can remove an instance for the factory and consequently, initializing a new one the next time it is invoked.
Now that we've seen the factory, let's look at one of the objects it creates.
<cfset variables.dsn = "">
<cfset variales.dbtype = "">
<cfset variables.tableprefix = "">
<cffunction name="init" access="public" returnType="forum" output="false"
hint="Returns an instance of the CFC initialized with the correct DSN.">
<cfargument name="factory" required="true" hint="factory">
<cfset variables.factory = arguments.factory>
<cfset variables.dsn = arguments.factory.getInstance('galleonSettings').getSettings().dsn>
<cfset variables.dbtype = arguments.factory.getInstance('galleonSettings').getSettings().dbtype>
<cfset variables.tableprefix = arguments.factory.getInstance('galleonSettings').getSettings().tableprefix>
<cfset variables.thread = arguments.factory.getInstance('thread') />
<cfset variables.utils = arguments.factory.getInstance('utils') />
<cfreturn this>
</cffunction>
..........
</cfcomponent>
You will note that the init method receives a single arguments: the instance of the factory. It stores a pointer to this instance, and uses it to request the settings object, and well as thread, and utils. As you can see, the forum object does not know where the other objects are, or how to initialize them. There is also a single instance of each one of these objects in memory, inside the factory to be thorough.
By doing this, we reduced the complexity. There's now a single place that knows where the objects are located and how to initialize them.
But now, every object depends on the factory ... there is nothing really wrong with this approach, but it can be improved. Your objects are coupled now to a single object, instead of to each other, but this approach is hard to unit-test, and it makes the components a little less portable.
Let's examine now the 'forums2' folder. You will immediately notice the similarity.. Application.cfm looks the same, there is an objectFactory.cfc... but this time, instead of passing the factory to all objects, it passes the dependencies to them, which is known as Dependency Injection.
Take a look at the new factory
Name : objectFactory.cfc
Author : Rob Gonda
Created : August 25, 2006
Last Updated : August 25, 2006
History :
Purpose : Simple Object Factory / Service Locator
--->
<cfcomponent displayname="objectFactory" hint="I am a simple object factory">
<!---
function init
in:
out: this
notes: usually initialized in application
--->
<cffunction name="init" access="public" output="No" returntype="objectFactory">
<cfscript>
// persistance of objects
variables.com = structNew();
</cfscript>
<cfreturn this />
</cffunction>
<!---
function getObject
in: name of object
out: object
notes:
--->
<cffunction name="createObj" access="public" output="No" returntype="any">
<cfargument name="objName" required="Yes" />
<cfscript>
switch(arguments.objName) {
case "conference":
return createObject('component','conference').init(
settings = getInstance('galleonSettings'),
forum = getInstance('forum'),
utils = getInstance('utils')
);
break;
case "forum":
return createObject('component','forum').init(
settings = getInstance('galleonSettings'),
thread = getInstance('thread'),
utils = getInstance('utils')
);
break;
case "galleonSettings":
return createObject('component','galleon');
break;
case "message":
return createObject('component','message').init(
settings = getInstance('galleonSettings'),
thread = getInstance('thread'),
forum = getInstance('forum'),
conference = getInstance('conference'),
user = getInstance('user'),
utils = getInstance('utils')
);
break;
case "rank":
return createObject('component','rank').init(
settings = getInstance('galleonSettings')
);
break;
case "thread":
return createObject('component','thread').init(
settings = getInstance('galleonSettings'),
utils = getInstance('utils')
);
break;
case "user":
return createObject('component','user').init(
settings = getInstance('galleonSettings'),
utils = getInstance('utils')
);
break;
case "utils":
return createObject('component','utils');
break;
}
</cfscript>
</cffunction>
<!---
function getInstance
in: name of object
out: object
notes: create a persistant object if doen not previously exists
--->
<cffunction name="getInstance" access="public" output="No" returntype="any">
<cfargument name="objName" required="Yes" />
<cfscript>
if ( not StructKeyExists(variables.com, arguments.objName) ){
variables.com[arguments.objName] = createObj(arguments.objName);
}
return variables.com[arguments.objName];
</cfscript>
</cffunction>
<cffunction name="removeInstance" access="public" output="No" returntype="void">
<cfargument name="objName" required="Yes" />
<cfscript>
if ( StructKeyExists(variables.com, arguments.objName) ){
structDelete(variables.com, arguments.objName);
}
</cfscript>
</cffunction>
</cfcomponent>
Nothing much changed, except that when initializing an object, it passes instances of other objects instead of the factory.
Let's look at the same forum object.
settings = getInstance('galleonSettings'),
thread = getInstance('thread'),
utils = getInstance('utils')
);
The factory is passing to the forum init function an instance of settings, thread, and utils. Because you're calling the getInstance method of the factory itself, it will run that code and get the instance of those objects prior to initializing the forum object. This method will recursively execute itself until initializing all dependencies, scope them all inside the factory, maintain a singleton of each, and then resolve and return back the requested object.
The forum object now does not receive the factory as an argument, but instead, it receives an instance of each object that it needs.
<cfset variables.dsn = "">
<cfset variales.dbtype = "">
<cfset variables.tableprefix = "">
<cffunction name="init" access="public" returnType="forum" output="false"
hint="Returns an instance of the CFC initialized with the correct DSN.">
<cfargument name="settings" required="true" hint="Setting">
<cfargument name="thread" required="true" hint="thread">
<cfargument name="utils" required="true" hint="utils">
<cfset variables.dsn = arguments.settings.getSettings().dsn>
<cfset variables.dbtype = arguments.settings.getSettings().dbtype>
<cfset variables.tableprefix = arguments.settings.getSettings().tableprefix>
<cfset variables.thread = arguments.thread />
<cfset variables.utils = arguments.utils />
<cfreturn this>
</cffunction>
.............
</cfcomponent>
Now you see why this pattern is called Dependency Injection... don't let the name scare you away, all it is doing is passing single instances each object that is dependent on another.
Hang on, we're almost there... 3 out of 4 examples shown.
The last one, is similar to the one we just examined... it uses the Dependency Injection pattern, but instead of a home-grown factory object, we are going to use ColdSpring.
Open your forums4 folder.
Application.cfm is very similar to the one we just saw, except that in ColdSpring the getInstance method is called getBean. Bean is nothing but an object, and the name comes from the Java world.
ColdSpring does not define the dependencies inside a CFC, but instead it allows you to configure them using an external XML file, which is passed to the ColdSpring component when initialized.
<cfset application.factory = createObject("component","coldspring.beans.DefaultXmlBeanFactory").init()>
<!--- load in our bean definitions --->
<cfset application.factory.loadBeans(expandPath("./config/ColdSpring.xml"))/>
<!--- Get main settings --->
<cfset application.settings = application.factory.getBean('galleonSettings').getSettings()>
&nbs
Factory method pattern implementation in coldfusion along with working sample code, This pattern helps to model an interface for creating an object which at creation time can let its subclasses decide which class to instantiate.
http://www.robgonda.com/blog/trackback.cfm?DD92DAE3-3048-7431-E43975AAD998B842
so all objects created by this object will be available though out the application.
if we retrieve that object with this object factory? Am i right?
its good to use this factory but we need to delete the other created
objects as variables.com will becoming a storage for number of objects?
confirm me so i can go ahead with this object factory