About a week ago I posted about my first experiments with object factories. If you haven't read that post, please do so before reading this one. When I wrapped the last entry I mentioned I had two problems with my solution. I want to thank Rob Gonda. His code (variations of it) is used below.
First - my factory returned a new instance every time you asked for a CFC. This isn't necessary if you only need one instance of the CFC per application. In other words, I may have multiple CFCs all using Foo.cfc, but only one instant needs to exist in memory. (Some people refer to this as a singleton.)
Let's look at a simple modification of the old factory to support this. Here is the code from the past entry:
<cfcomponent output="false">
<cffunction name="getComponent" returnType="any" output="false">
<cfargument name="name" type="string" required="true">
<cfswitch expression="name">
<cfcase value="ship">
<cfreturn createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses")>
</cfcase>
<cfcase value="soldier">
<cfreturn createObject("component","cfcs.soldier").init("dsn", "arrayofjobs")>
</cfcase>
<cfcase value="planet">
<cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplanettypes","maxforgame")>
</cfcase>
<cfcase value="player">
<cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplayertypes","maxforgame")>
</cfcase>
<cfcase value="ruleset">
<cfreturn createObject("component", "cfcs.ruleset").init("dsn")>
</cfcase>
<cfdefaultcase>
<cfthrow message="#arguments.name# is not a recognized component.">
</cfdefaultcase>
</cfswitch>
</cffunction>
</cfcomponent>
The idea is that your code will use factory.getComponent(name) to load a CFC. As you can see though that a new CFC is loaded every time you request the CFC. Now lets tweak it a bit...
<cfcomponent output="false">
<cfset variables.instances = structNew()>
<cffunction name="getComponent" returnType="any" output="false">
<cfargument name="name" type="string" required="true">
<cfswitch expression="name">
<cfcase value="ship">
<cfreturn createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses")>
</cfcase>
<cfcase value="soldier">
<cfreturn createObject("component","cfcs.soldier").init("dsn", "arrayofjobs")>
</cfcase>
<cfcase value="planet">
<cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplanettypes","maxforgame")>
</cfcase>
<cfcase value="player">
<cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplayertypes","maxforgame")>
</cfcase>
<cfcase value="ruleset">
<cfreturn createObject("component", "cfcs.ruleset").init("dsn")>
</cfcase>
<cfdefaultcase>
<cfthrow message="#arguments.name# is not a recognized component.">
</cfdefaultcase>
</cfswitch>
</cffunction>
<cffunction name="get" returnType="any" output="false">
<cfargument name="name" type="string" required="true">
<cfif not structKeyExists(variables.instances, arguments.name)>
<cfset variables.instances[arguments.name] = getComponent(arguments.name)>
</cfif>
<cfreturn variables.instances[arguments.name]>
</cffunction>
</cfcomponent>
First - note that the CFC creates a structure, variables.instance, outside of any method. This means it will run when the CFC is created. Next note the new method, get. This method will see if a component exists in the instances structure. If not, it adds it. Lastly the CFC stored in the structure is returned. So the first time you ask for "moo" the CFC will be created, but after that the initial instance is returned. One more thing I like about this change is that I use get instead of getComponent. It's less typing, and since the factory only returns components, it makes it even simpler.
Ok - so to repeat: This change simply ensures that I return one and only one instance of a CFC. So the next thing to we need to cover is how our CFCs themselves will talk to the factory. It would be child's play to add this to our CFCs:
<cfset variables.foo = application.factory.get("goo")>
However, it is generally considered bad practice to reference the Application scope inside of a CFC. So how do we get what we need? One answer is dependency injection.
I'll be honest. Most of the time when I heard dependency injection, my left eye would twitch a bit and I generally felt like ducking my head into the ground. I don't know why but it really seemed weird to me.
So I finally got it through my thick head. (Probably thanks to all the exposure at the frameworks conference.) Imagine you are going to Home Depot because you want your bathroom redone. You could buy the materials, hire the designer, plumber, carpenter, etc.
Or you could hire the designer and let Home Depot supply him with everything he needs. In other words, Home Depot will provide the designer with all the resources he needs to complete the job. The designer doesn't have to worry about where it comes from, it is just there.
I don't know if that is the best description (and I know my readers will find better ones), but lets show an example.
The CFC Ship needs an instance of planet, soldier, and ruleset. (By the way, there was some confusion in the last blog entry. No - I am not working on a Star Wars ColdFusion site. The CFC names are just for fun.) The old Ship.cfc init() method could have looked like this:
<cffunction name="init" returnType="ship">
<cfargument name="dsn" type="string">
<cfargument name="arrayofshipclasses" type="array">
<cfset variables.planet = createObject("component", "planet")>
<cfset variables.soldier = createObject("component", "soldier")>
<cfset variables.ruleset = createObject("component", "ruleset")>
<cfreturn this>
</cffunction>
As I mentioned - when the requirements for these CFCs change, I have to update ship.cfc properly or my application will break. I'm going to switch this CFC so that now the the CFCs Ship needs are passed to it when it is created:
<cffunction name="init" returnType="ship">
<cfargument name="dsn" type="string">
<cfargument name="arrayofshipclasses" type="array">
<cfargument name="planet" type="planet" required="true">
<cfargument name="soldier" type="planet" required="true">
<cfargument name="ruleset" type="planet" required="true">
<cfset variables.planet = arguments.planet>
<cfset variables.soldier = arguments.soldier>
<cfset variables.ruleset = arguments.ruleset>
<cfreturn this>
</cffunction>
Notice that the CFC has zero idea how these CFCs are made. It just gets them handed to it. The factory would be updated of course. Here is the relevant changes from factory.cfc:
<cfcase value="ship">
<cfreturn createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses", get("planet"), get("soldier"), get("ruleset"))>
</cfcase>
Not terribly complex. But certainly if there is any complexity, its in the factory now, and not my CFCs. All issues related to component creation are now contained within one file. This is a much better situation than how the code was before. For a good example - check out Galleon. While I'm proud of my forums - it really shows you where a factory can help out.
Comment away folks and let me know what I screwed up. If anything isn't clear - let me know!
Archived Comments
In the initial code snippets, it looks like 'player' creates an instance of 'planet'.
In the 2nd to last code snippet, the cfargument types are all planet.
If I'm reading this correctly, it looks like when you call a Factory for "ship", it is using the get method to try to retrieve component instances that were never created. Should the last code segment call "getComponent" instead of just 'get'?
Jeff, the get call for ship works fine. See that it uses get methods which will recursively resolve their own dependencies. The get function invokes the getComponent method, and then stores the instance of the object inside the variables.instances structure.
Ray, thanks for posting this and forcing me to write about my sessions. I just posted a small summary of what was covered at http://www.robgonda.com/blo...
Cheers.
Rob,
That was the piece I needed. I didn't see that 'get' called 'getComponent'. The code makes a lot more sense understanding that.
The questions now is, why store the new component as an instance variable in the factory instead of just passing it around as a parameter?
Jeff,
The reason to store it inside the factory is because it needs to persist. You should get the same instance each time you get that object.
E.g.
--inside user controller--
cfset emailService = application.factory.get('emailService')
--inside logService controller--
cfset emailService = application.factory.get('emailService')
In essence, both emailService variables have to use the same instance of the object. The only way to achieve this to make the factory store the instance.
Another reason is lazy loading. You will note that the factory does not initialize any component until first load. However, if the userService needs the loggingService, but the loggingService gets initialized right away, and the userService only gets initialized a few minutes after, you will still need the same instance of the loggingService to be passed to the userService. Making the factory store these instances solves the problem.
HTH
It seems to me that there are cases where you would want a new object of the same type, not the same object. Is that also correct in your version of the world?
In Ray's sample to date, I see no way to differentiate between "get new" and "get preexisting".
@Jeff - first comment: It was a typo. I will fix it after I reply to the comments.
@Rob - thanks for replying while I was in dreamland. :)
@Jeff - yes - as Rob said, "get" is the core API to get a component now. Sorry I didn't make that clear.
Lastly - yes - this facgtory ALWAYS returns a singleton. Later today I'll blog a mod that lets you get new instances.
One of the things I have trouble with getting through to some of my own staff is how to identify when you can use a singleton and when you can't. You may want to cover that for your readers.
I generally use ColdSpring for all of this - I would only custom-code a factory when you're going to have some decision-making process in how your dependencies get resolved.
Craig, I intentionally did NOT use ColdSpring. Not because I think it is bad - but because I am _just_ getting into this topic. You have to walk before you run... :)
Ray,
A while ago, I blogged about this same subject, the result was a collaboration between Nic Tunney, Kelly Brown and myself. We have created an ObjectFactory that works much like a lite version of ColdSpring. You can register objects via an xml file, tell which objects are singletons, and pass other objects into the constructor method so your objects don't have to carry that burden. You can check it out here:
http://www.phillnacelli.net...
http://www.nictunney.com/in...
http://www.kellyjo.com/blog...
All the best, have a great Mardi Gras!
Phill, I didn't know. I'll definitely check it out. That is a -very- smart list of people. :)
Being fairly new to using factories and the like in CF can someone maybe briefly discus the performance impact using the different factory approaches (coldspring vs dependency etc) vs just initializing the objects in your onappstart. Given the size of an application I always have a hard time wrapping my head around adding complexity sometimes (similar to the joelonsoftware factory factory to make factory which will magically deliver your hammer analogy). Thanks much.
I think the point here is to reduce complexity. If you look at the first blog entry where I showed all the createObjects - to me that was more complex then simple foo = factory.get("name") calls.