Baby steps in factory land
About a week or so ago I attend a presentation on object factories by Rob Gonda. Object factories were something I had heard about and read about, but didn't quite get. The presentation really helped a lot, and I thought I'd share my thoughts on how I understand this concept, and then quickly show a practical example of how I applied this to the new beta of Canvas.
First lets talk a bit about how a person typically creates a set of components for use within an application. Most folks will (hopefully) create these components in the Application scope so they only have to create it on time. So let's start with a simple onApplicationStart method:
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfset application.ship = createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses")>
<cfset application.soldier = createObject("component","cfcs.soldier").init("dsn", "arrayofjobs")>
<cfreturn true>
</cffunction>
This application uses 2 CFCs (Ship and Soldier). Each CFC has 2 arguments necessary to initialize them. So far so good. If either of the CFCs change then it isn't a big deal.
Where things get complex is when you begin to add a few more CFCs to the mix:
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfset application.ship = createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses")>
<cfset application.soldier = createObject("component","cfcs.soldier").init("dsn", "arrayofjobs")>
<cfset application.planet = createObject("component", "cfcs.planet").init("dsn","arrayofplanettypes","maxforgame")>
<cfset application.player = createObject("component", "cfcs.planet").init("dsn","arrayofplayertypes","maxforgame")>
<cfset application.ruleset = createObject("component", "cfcs.ruleset").init("dsn")>
<cfreturn true>
</cffunction>
So that isn't terribly bad. It's a bit messy, but ok. But let me throw a monkey wrench into the process. What if....
- Ship.cfc needs it's own planet, solder, and ruleset cfc?
- Solder.cfc needs a copy of ruleset
- Planet.cfc needs a copy of soldier, ruleset
- Player.cfc needs a copy of ship,solder,planet, and ruleset.
Um, so I can handle that. Each of the CFCs above, in their init methods, will simply have their own creatObjects. While I used to have 5 CFCs cached I now have 15. Well, RAM is cheap so I'm not too worried, and it's not like the CFCs take up a lot of RAM anyway. But what is more troublesome is the thought of change. If the required attributes for ruleset change, now I have to update multiple CFCs as well as Application.cfc. And guess what happens if I forget?
So the first thing a factory can help me out with is simply handling creating objects for me. Let's build a super simple factory. Consider the code below:
<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>
All I've done is created a function that - based on a name passed in, will return an instance of the component. Where things really get nice then is back in the Application.cfc file. Look at the change:
<cffunction name="onApplicationStart" returnType="boolean" output="false">
<cfset application.factory = createOject("component", "cfcs.factory")>
<cfset application.ship = application.factory.getComponent("ship")>
<cfset application.soldier = application.factory.getComponent("soldier")>
<cfset application.planet = application.factory.getComponent("planet")>
<cfset application.player = application.factory.getComponent("player")>
<cfset application.ruleset = application.factory.getComponent("ruleset")>
<cfreturn true>
</cffunction>
Wow - a lot simpler, right? And if a component's needs change - I can edit the factory and not change anything else in Application.cfc.
What this doesn't yet fix are two issues: How do my CFCs, which each needs instances of each other, get their instances? Sure they can also use application.factory, but that breaks encapsulation. Secondly - my factory makes a new instance of a CFC for every request. Is there anyway to make that nicer?
To be continued...
Comments
The next step, like Steve said, is to save the reference to the instances inside your factory. Order may or may not be as important if you use the same factory methods recursively to wire your objects.
<pre><cfset application.soldier = application.factory.getComponent("soldier")>
"arrayofjobs")></pre>
After the bullet list, "Each of the CFCs aboVe" ? Is a V missing?
That said, I think I'm missing some background with this example. I don't understand what ship, soldier, planet, player, and ruleset are. Without knowing that, I can't say whether or not it makes sense to create the components in the application scope. Perhaps I'm looking to deep into the example.
To date, I'm with Steve Nelson. I see no benefit in encapsulating a single line of code. I assume this will make more sense as you write more?
Typo fixed. Thanks.
You don't see the benefit already? Well hopefully the second post will make it more obvious.
http://www.phillnacelli.net/blog/index.cfm/2006/11...
http://www.kellyjo.com/blog/index.cfm/2006/12/11/D...
Michael
I'm definitely putting too much into the code. I tend to learn better with real-world examples than I do on 'theoretical' examples.

The major problem here, of course, is that order becomes important. Still, you could have your code throw an informative error if the required component wasn't available.
If the factory does store an instance, you could also have it verify if it has the instance stored before it tries to create a new one.