For what it's worth this is how I solved the problem of not being able to return a "null" CFC. To reiterate the problem: When a CFC method accepts or returns a datatype of CFC it's difficult to do properly if the parameter is optional. If you set the return type to the component type, for example you MUST return an object. However if that property is optional the return type can't be set to the component type as an error will be thrown. Also, the consumer of the data can't assume that a component has been returned and must do a structural check to determine the output (for example an "IsSimpleValue" or somesuch). The bottom line is that this is handled by null in most languages. Null can be passed as a value in method returns that would optionally return object types. ColdFusion lacks the concept for nulls so a workaround must be used. My goal was to: 1) Allow methods that return component data types to ALWAYS return the component datatype. I feel this eliminates the need for consumer components/pages to "know too much" about the internal workings of the component and allows the return type to be automatically protected by the "returntype" attribute. 2) Make it easy to create and check for the existence of a "nullish" return - in other words a way to make the return optional (but maintain the proper return type) and still make it easy to determine. 3) Standardize (at least for myself) the process so that it works with all components. I'm not sure if my solution is unique or even "right" - but it does seem to do the trick and (I think) it's fairly elegant. But I'd appreciate any comments. Anyway: I began by writing a "root" component, named (oddly enough) root.cfc. This component contains: <cfcomponent hint="Provides basic implentation methods for all DP components."> <!--- Constructor ---> <cfset this.LockID = UUIDAsVar()> <cfset this.Null = true> <cffunction name="new" hint="The component constructor." access="public" output="No"> <!--- Set implementation vars ---> <cfset this.Null = false> </cffunction> <cffunction name="getLockID" hint="Returns the LockID for the instance. Useful in named locks for serializing access to the component instance." returntype="string" access="public" output="No"> <cfreturn this.LockID> </cffunction> <cffunction name="isNull" hint="Returns the value of this.Null - will be true before the new() method is called; false after." returntype="boolean" access="public" output="No"> <cfreturn this.Null> </cffunction> <cffunction name="UUIDAsVar" hint="Returns a UUID() formatted for use as a variable/key name (all dashes replaced with underscores and prepended with an x" returntype="string" access="private" output="No"> <cfset var FormattedUUID = "x" & Replace(CreateUUID(), "-", "_", "All")> <cfReturn FormattedUUID> </cffunction> </cfcomponent> Note that upon creation a value "this.Null" is set to True. However when the new() method is run it's set to False. Also, I've attempted to solve the problem of locking component operations by having this root class creates a unqiue "LockID" using a UUID as a base. You can then use this value as the name of a lock to serialize access to components. I still haven't quite worked this last bit out... but the idea seems sound. For example if you have a "collection" component that returns a pointer to a structure of references you might name lock it (with this name) to prevent another thread from running a "removeComponent()" on the collection thus removing a component from the structure your traversing. Anyway - this root component is extended by all other components in the package. A simple example called (again, oddly) "Sample.cfc" (sorry for the lack of comments but I'm actually, surprisingly, trying to keep this short): <cfcomponent extends="Root"> <cffunction name="new"> <cfargument name="Name" type="String" required="Yes"> <cfargument name="Parent" type="Sample" required="No"> <cfset super.new()> <cfset this.name = arguments.name> <cfif IsDefined("arguments.Parent")> <cfset this.Parent = arguments.Parent> <cfelse> <cfset this.Parent = CreateObject('Component', 'Sample')> </cfif> </cffunction> <cffunction name="getName" returntype="string" output="No"> <cfreturn this.Name> </cffunction> <cffunction name="setName"> <cfargument name="Name" type="String" required="Yes" output="No"> <cfset this.Name = arguments.Name> </cffunction> <cffunction name="getParent" returntype="Sample" output="No"> <cfreturn this.Parent> </cffunction> <cffunction name="setParent"> <cfargument name="Parent" type="Sample" required="Yes" output="No"> <cfset this.Parent = arguments.Parent> </cffunction> </cfcomponent> This component takes a string "name" and "parent" that is of type "Sample" (in other words Sample components can be contained in a tree of parents and children). Not all Samples have parents, but some do. Note also that Sample extends Root AND, in the new() method calls the super.new() method (which as we saw sets the boolean value of this.null). The important thing is that the new() method will set the value of "Parent" to a Sample component if one is passed. However if one is NOT passed it will create a new Sample component (but it will NOT call the new() method on it). This means that you can easily determine if the Parent has a value (a populated sample component) or "null". For example: <cfset mySample = CreateObject('Component', 'Sample')> <cfinvoke component="#mySample#" method="New"> <cfinvokeargument name="Name" value="First Sample"> </cfinvoke> <cfset myChildSample = CreateObject('Component', 'Sample')> <cfinvoke component="#myChildSample#" method="New"> <cfinvokeargument name="Name" value="Second Sample"> <cfinvokeargument name="Parent" value="#mySample#"> </cfinvoke> <cfoutput> Sample "#mySample.getName()#" <cfif mySample.Parent.isNull()> has no parent.<br> <cfelse> has a parent and it's "#mySample.Parent.getName()#".<br> </cfif> Sample "#myChildSample.getName()#" <cfif myChildSample.Parent.isNull()> has no parent.<br> <cfelse> has a parent and it's "#myChildSample.Parent.getName()#".<br> </cfif> </cfoutput> Note that you can just say "mySample.Parent.isNull()" (which always returns a Boolean response) to determine the disposition of the parent. Since the method isNull() is defined in the Root component it will work for ANY component which extends it. The return is also ALWAYS of the proper type. The way I have it here is simplest, but you are potentially creating a lot of "null" objects. You could also choose to create a single "null" object of each type and reference that like mad if the total number of instantiated components worries you. Although remember that a component (especially before initialization via the new() method) takes up VERY little space or resources. You may also decide, if you like, to default the values of all properties in the components upon creation. I didn't, but you could. That's about it. Sorry this is so long, but I spent so long trying to come up with a solution to this problem that worked for me I thought others may be in the same boat. I'd love to hear comments/criticisms on this. Jim Davis ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
[Todays Threads] [This Message] [Subscription] [Fast Unsubscribe] [User Settings]

Reply via email to