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