Baby steps in factory land (again)

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”>

  &lt;cfcase value="ship"&gt;
     &lt;cfreturn createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfcase value="soldier"&gt;
     &lt;cfreturn createObject("component","cfcs.soldier").init("dsn", "arrayofjobs")&gt;
  &lt;/cfcase&gt;

  &lt;cfcase value="planet"&gt;
     &lt;cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplanettypes","maxforgame")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfcase value="player"&gt;
     &lt;cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplayertypes","maxforgame")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfcase value="ruleset"&gt;
     &lt;cfreturn createObject("component", "cfcs.ruleset").init("dsn")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfdefaultcase&gt;
     &lt;cfthrow message="#arguments.name# is not a recognized component."&gt;
  &lt;/cfdefaultcase&gt;

</cfswitch>

</cffunction>

</cfcomponent> </code>

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”>

  &lt;cfcase value="ship"&gt;
     &lt;cfreturn createObject("component", "cfcs.ship").init("dsn", "arrayofshipclasses")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfcase value="soldier"&gt;
     &lt;cfreturn createObject("component","cfcs.soldier").init("dsn", "arrayofjobs")&gt;
  &lt;/cfcase&gt;

  &lt;cfcase value="planet"&gt;
     &lt;cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplanettypes","maxforgame")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfcase value="player"&gt;
     &lt;cfreturn createObject("component", "cfcs.planet").init("dsn","arrayofplayertypes","maxforgame")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfcase value="ruleset"&gt;
     &lt;cfreturn createObject("component", "cfcs.ruleset").init("dsn")&gt;
  &lt;/cfcase&gt;
  
  &lt;cfdefaultcase&gt;
     &lt;cfthrow message="#arguments.name# is not a recognized component."&gt;
  &lt;/cfdefaultcase&gt;

</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> </code>

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> </code>

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> </code>

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!

Raymond Camden's Picture

About Raymond Camden

Raymond is a developer advocate. He focuses on JavaScript, serverless and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Comments