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

Steve Bryant's Gravatar Could you have your factory store an instance before it returns it? The the factory itself would have an instance available to pass in to a new component as it creates it.

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.
# Posted By Steve Bryant | 2/11/07 9:26 AM
Dan's Gravatar Have you had a chance to check out ColdSpring yet? It really helps out with dependency injections, really easy to use and very powerful.
# Posted By Dan | 2/11/07 10:35 AM
Steve Nelson's Gravatar I just worked on a BIG project that used factories exactly like this. It's not easier to deal with.
# Posted By Steve Nelson | 2/11/07 10:41 AM
Rob Gonda's Gravatar Nice Ray ... great to see you're playing with Factories. I'm late to release the code and slides for my presso, but I'll do it today.

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.
# Posted By Rob Gonda | 2/11/07 10:55 AM
Raymond Camden's Gravatar Dan - note the title: "Baby steps" :)
# Posted By Raymond Camden | 2/11/07 11:09 AM
Don Zacharias's Gravatar this last line in your last code snippet is just an errant copy-paste, right?
<pre><cfset application.soldier = application.factory.getComponent("soldier")>
"arrayofjobs")></pre>
# Posted By Don Zacharias | 2/11/07 11:59 AM
Raymond Camden's Gravatar Dan, no, it was a test. You passed. ;) Seriously - thanks. I also found another typo.
# Posted By Raymond Camden | 2/11/07 12:08 PM
Jeff Houser's Gravatar 2nd code block, are 'player' and 'planet' variables supposed to be created from the same 'planet' cfc?

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?
# Posted By Jeff Houser | 2/11/07 5:06 PM
Raymond Camden's Gravatar Jeff, I think you are reading a bit too much into the code. The whole ship, player, planet, etc, were just examples. They don't mean _anything_ or relate to a real world application. My second post _will_ have a real example and will show the above and after.

Typo fixed. Thanks.

You don't see the benefit already? Well hopefully the second post will make it more obvious.
# Posted By Raymond Camden | 2/11/07 5:14 PM
Michael Sharman's Gravatar On the topic of factories here are 2 great blog posts which I found useful a little while ago when researching factories:

http://www.phillnacelli.net/blog/index.cfm/2006/11...

http://www.kellyjo.com/blog/index.cfm/2006/12/11/D...

Michael
# Posted By Michael Sharman | 2/11/07 5:32 PM
Jeff Houser's Gravatar Ray,

I'm definitely putting too much into the code. I tend to learn better with real-world examples than I do on 'theoretical' examples.
# Posted By Jeff Houser | 2/11/07 5:35 PM
jim collins's Gravatar I like factories, but whenever I read about them I'm reminded of this article: http://discuss.joelonsoftware.com/default.asp?joel...
# Posted By jim collins | 2/12/07 10:15 AM
Rob Gonda's Gravatar Jim, that's a great story. In our defense, may I add, Dependency Injection would simplify your dilemma. By using DI, the factory becomes transparent, thus w/o the need of any manual. If you need a hammer, it will magically appear in your hands.
# Posted By Rob Gonda | 2/12/07 9:52 PM