Rob Gonda's Blog

Object Factories and Circular Dependencies

In my last post about Object Factories I provided very basic examples to get you up and running quickly, and mentioned that it would not work with circular dependencies. A circular dependency means that two objects depend on each other. If you try to create them and pass each other on the constructor (init) method, you will create an infinity loop.

E.g.

objA = createObject('component', 'objA').init( objB = get('objB');
this will resolve into getting objB
objB = createObject('component', 'objA').init( objA = get('objA');
this will try to create objA, but since that action hasn't completed yet, it will create a new one, and keep creating new objects recursively.

So what's the solution? using setters methods instead of constructor methods. This simply means that you would first create an object, and then once created, pass all dependencies by executing setObjA, setObjB, setObjN...

Here's the new Galleon object factory that allows for circular dependencies

<!---
    Name         : objectFactory.cfc
    Author       : Rob Gonda
    Created      : August 25, 2006
    Last Updated : February 22, 2007
    History      :
    Purpose         : Simple Object Factory / Service Locator
--->
<cfcomponent displayname="objectFactory" hint="I am a simple object factory">

    <!---
        function init
        in:       
        out:    this
        notes:    usually initialized in application
     --->
    <cffunction name="init" access="public" output="No" returntype="objectFactory">
   
        <cfscript>
            // persistance of objects
            variables.com = structNew();
        </cfscript>

        <cfreturn this />
    </cffunction>

    <!---
        function getObject
        in:        name of object
        out:    object
        notes:   
     --->
    <cffunction name="get" access="public" output="No" returntype="any">
        <cfargument name="objName" required="false" type="string" />
        <cfargument name="singleton" required="false" type="boolean" default="true" />
       
        <cfscript>
            var obj = ''; //local var to hold object
            if (arguments.singleton and singletonExists(arguments.objName)) {
                return getSingleton(arguments.objName);
            }
       
            switch(arguments.objName) {
                case "conference":
                    obj = createObject('component','conference').init();
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                        obj.setSettings( get('galleonSettings', arguments.singleton) );
                        obj.setForum( get('forum', arguments.singleton) );
                        obj.setUtils( get('utils', arguments.singleton) );
                    return obj;
                break;

                case "forum":
                    obj = createObject('component','forum').init();
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                        obj.setSettings( get('galleonSettings', arguments.singleton) );
                        obj.setThread( get('thread', arguments.singleton) );
                        obj.setUtils( get('utils', arguments.singleton) );
                    return obj;
                break;

                case "galleonSettings":
                    obj = createObject('component','galleon');
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                    return obj;
                break;

                case "message":
                    obj = createObject('component','message').init();
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                        obj.setSettings( get('galleonSettings', arguments.singleton) );
                        obj.setThread( get('thread', arguments.singleton) );
                        obj.setForum( get('forum', arguments.singleton) );
                        obj.setConference( get('conference', arguments.singleton) );
                        obj.setUser( get('user', arguments.singleton) );
                        obj.setUtils( get('utils', arguments.singleton) );
                    return obj;
                break;

                case "rank":
                    obj = createObject('component','rank').init();
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                        obj.setSettings( get('galleonSettings', arguments.singleton) );
                        obj.setUtils( get('utils', arguments.singleton) );
                    return obj;
                break;

                case "thread":
                    obj = createObject('component','thread').init();
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                        obj.setSettings( get('galleonSettings', arguments.singleton) );
                        obj.setUtils( get('utils', arguments.singleton) );
                        obj.setMessage( get('message', arguments.singleton) );
                    return obj;
                break;

                case "user":
                    obj = createObject('component','user').init();
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                        obj.setSettings( get('galleonSettings', arguments.singleton) );
                        obj.setUtils( get('utils', arguments.singleton) );
                    return obj;
                break;

                case "utils":
                    obj = createObject('component','utils');
                        if (arguments.singleton) { // scope singleton
                            addSingleton(arguments.objName, obj);
                        }
                        // inject dependencies through setter
                    return obj;
                break;

            }
        </cfscript>
       
    </cffunction>
   
   
    <cffunction name="singletonExists" access="public" output="No" returntype="boolean">
        <cfargument name="objName" required="Yes" type="string" />
        <cfreturn StructKeyExists(variables.com, arguments.objName) />
    </cffunction>
   
    <cffunction name="addSingleton" access="public" output="No" returntype="void">
        <cfargument name="objName" required="Yes" type="string" />
        <cfargument name="obj" required="Yes" />
        <cfset variables.com[arguments.objName] = arguments.obj />
    </cffunction>

    <cffunction name="getSingleton" access="public" output="No" returntype="any">
        <cfargument name="objName" required="Yes" type="string" />
        <cfreturn variables.com[arguments.objName] />
    </cffunction>

    <cffunction name="removeSingleton" access="public" output="No" returntype="void">
        <cfargument name="objName" required="Yes" />
        <cfscript>
            if ( StructKeyExists(variables.com, arguments.objName) ){
                structDelete(variables.com, arguments.objName);
            }
        </cfscript>
    </cffunction>

</cfcomponent>


You will notice that I didn't try to create the object, execute the setters, and then return it. Instead, I create the object, check if I should scope it as a singleton, scope it, and then execute the setters. The reason for this is to break the infinite recursion. I need to scope it before my setters try to get a new/existing instance of an object. Once I scope it, the next object will be able to get it w/o having to complete the full initialization.

I sent Ray Camden my version of Galleons a few months ago as an exercise, but I intentionally left out a couple of wires to break circular dependencies. I assume Ray checked the code and fixed the pieces of code where I cheated, creating the circular dependencies... but the factory I sent did not support this. Here's the updated one, so expect to see it at the Galleon RIAForge page soon.

Related Blog Entries

TrackBacks
There are no trackbacks for this entry.

Trackback URL for this entry:
http://www.robgonda.com/blog/trackback.cfm?ED39F985-3048-7431-E4EC9417DBE28167

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
This blog is running version 5.9.003. Contact Blog Owner