An Evenings Craziness
What’s a guy to do?
Well, I though it might be nice to prototype a very basic DI factory which is capable of lazy loading, is lightweight enough to use for all of your objects and which uses an included config file for people who swing that way. I wrote this in under three hours and it depends on a base class. It isn’t pretty, but I thought it was kinda fun. The code is up and running on a test project so it works.
[UPDATE] OK, so I have some circular dependencies so I WILL add proper dependency resolution when I get into the office. It's actually quite easy if you use object based mixins to drop the objects in after instantiation. It is just a matter (if you lazy load) of building up a dependency tree recursively and then creating all the objects (that don't already exist) first before then injecting the dependencies into each one. I'm really bad with recursion but will try to drop this in shortly. I may also put a zip file together with a readme if anyone thinks they would ever want to play with such a thing. Again if you're interested let me know with a comment below. No votes = no interest = no code :-> [/UPDATE]
The core is a single cfc called classforge which just has an init method and a getSingleton method that returns a fully loaded singleton.
<cffunction name="init" returntype="ClassForge" access="public" output="false" hint="I initialize the ClassForge factory with application specific parameters.">
<cfinclude template="../config.cfm">
<cfreturn This>
</cffunction>
<cffunction name="getSingleton" returntype="any" access="public" output="false" hint="I return a singleton.">
<cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to generate.">
<cfset var Local = StructNew()>
<cfscript>
// If the singleton doesn't exist, lazy load it if(not isDefined("variables.Singleton.#ObjectName#"))
{
// Get the configured object path Local.ObjectPath = variables.Object[ObjectName].Path;
// Get the initialization data (if any) If (StructKeyExists(variables,ObjectName))
{Local.InitStruct = variables[ObjectName];}
Else
{Local.InitStruct = StructNew();};
// Initialize the object variables.Singleton[ObjectName] = CreateObject("component","#application.CFMapping#.#Local.ObjectPath#.#ObjectName#").Init(Local.InitStruct);
// Add any dependent object using the AddObject base method (only in services for now) If (IsDefined("variables.Dependencies.#ObjectName#"))
{
// variables.Dependencies[ObjectName] For (Local.Count = 1; Local.Count lte listlen(variables.Dependencies[ObjectName]); Local.Count = Local.Count + 1)
{
Local.RequiredObjectName = listgetat(variables.Dependencies[ObjectName], Local.Count);
Local.RequiredObject = variables.getSingleton(Local.RequiredObjectName);
variables.Singleton[ObjectName].AddObject(Local.RequiredObjectName,Local.RequiredObject);
};
};
};
// Get the Singleton Local.ReturnSingleton = variables.Singleton[ObjectName];
</cfscript>
<cfreturn Local.ReturnSingleton>
</cffunction>
</cfcomponent>
You’ll notice that the init method includes config.cfm which allows you to just set variables using cfscript syntax. You lose all of the provability benefits of XML with a DTD, but I’d be generating the config data anyway so I can validate it pre-generation.
<!--- I get mixed into the classforge init method --->
<cfscript>
variables.Object.PageService.Path = "com.custom.model.Page";
variables.Object.PageDAO.Path = "com.custom.model.Page";
variables.Object.UserService.Path = "com.custom.model.User";
variables.Object.UserDAO.Path = "com.custom.model.User";
// Page Service variables.PageService.ObjectName = "Page";
variables.PageService.DefaultFilePath = "home";
variables.PageService.ObjectTitle = "Page";
variables.PageService.DefaultAdminListFieldList = "Title";
variables.PageService.DefaultAdminFilter = "1 = 1";
variables.PageService.DefaultAdminOrder = "Title";
variables.PageService.DefaultAdminAddFieldNameList = "Title,Content,Display,DisplayOrder";
variables.PageService.DefaultAdminEditFieldNameList = variables.PageService.DefaultAdminAddFieldNameList;
variables.Dependencies.PageService = "PageDAO";
// User Service variables.UserService.ObjectName = "User";
variables.UserService.ObjectTitle = "User";
variables.UserService.DefaultAdminListFieldList = "FirstName,LastName,Email";
variables.UserService.DefaultAdminFilter = "1 = 1";
variables.UserService.DefaultAdminOrder = "LastName,FirstName";
variables.UserService.DefaultAdminAddFieldNameList = "FirstName,LastName,Email,Password";
variables.UserService.DefaultAdminEditFieldNameList = variables.UserService.DefaultAdminAddFieldNameList;
// Page DAO variables.PageDAO.TableName = "tbl_Page";
variables.PageDAO.ViewName = "tbl_Page";
variables.PageDAO.PropertyNameList = "*";
variables.PageDAO.IDProperty = "PageID";
// User DAO
variables.UserDAO.TableName = "tbl_User";
variables.UserDAO.ViewName = "tbl_User";
variables.UserDAO.PropertyNameList = "*";
variables.UserDAO.IDProperty = "UserID";
</cfscript>
Then you need a standard init method which I just cfinclude (class based mix in) to all my classes:
<cfargument name="ArgumentStruct" type="struct" required="no">
<cfscript>
If (IsDefined("arguments.ArgumentStruct"))
{StructAppend(variables,arguments.ArgumentStruct);};
</cfscript>
<cfreturn This>
</cffunction>
Finally, I threw in a base method (I know, I know, but THREE HOURS people – give me a break) that allows you to add an the object.
<cfargument name="ObjectName" type="string" required="yes" hint="I am the name of the object to add.">
<cfargument name="Object" type="any" required="yes" hint="I am the object to add.">
<!--- Declare a local structure for all local variables --->
<cfset variables[ObjectName] = Object>
</cffunction>
And with this, you just provide a comma delimited list of the dependencies for each object in your config file that has dependencies and you can then call those methods in your class using variables.ObjectName syntax – just like CS.
Don’t bother telling me this isn’t CS, I get that. Dave and Chris are way smarter than me and probably spent more than an evening creating CS. This is, however kinda cool. It is NOT designed to do complex dependency resolution because I’m a simple guy writing simple apps so I just don’t need that. I may throw in some AOP style features one evening next week using object based mixins as as I posted on a couple of months back.
Well, at least I’m posting code now. Crazy code, bad code, but code . . . Oh, and if you caught that the init file doesn't allow for a Super.Init() call, you could tweak it so that it supported that, but I've decided to go ahead and mix in all of my inheritance trees (base classes generated classes and custom classes) just to see what that's like. I'll report on that once I've build a real application with it.
Couple of other things. You create the factory as follows. I do it in an index.cfm controller:
Then you can get a service object or something similar by calling:



There are no comments for this entry.
[Add Comment]