By Peter Bell

Snippets: A Generic Getter

I used to hate generic getters. Martin Fowler even has a term for people like me who don't think your beans should just go around sharing all of their properties (whether using generic, generated or hand coded getters). But then I realized how to create a smarter generic getter that would distinguish attributes from properties and allow for much shyer behavior and then I was sold on generic getters.

I love dynamic programming patterns. All other things being equal, the less code in your application, the eaiser it is to code, debug and maintain. Thoughtfully applied dynamic programming can drastically cut the amount of code in your application, making it much more maintainable and flexible. Imagine having a single generic base getter for all of your objects that would support information hiding, remove the need to test for existance, and that would seamlessly support overloading by custom getters where required. That's what I'm playing with here and it seems (so far) to be working very well . . .

Lets start by looking at the design decisions, and then I'll share the (fairly trivial) code.

Firstly, I wanted to make sure that my generic getter wouldn't just share all of the properties loaded into my iterating business objects, so I added a private variables.gettableAttributeList() property to my base bean with a comma delimited list of the attributes I wanted to share. It does support a * to share all of the internal properties, but that is more for quick prototyping than for an actual system and I'll probably remove that feature as I start generating the business objects again. If an attribute name isn't on that list for the particular object called, the generic getter won't even try to return it.

Secondly I didn't want to get ColdFusion errors just because someone called for an attribute that didn't exist. I'm not sure how to handle this for the long term, but right now the method can either return a null string or an informative error message if you get an attribute that doesn't exist. The null string is very useful for the Input facade I use to wrap form and URL variables as I can always just call Input.get('whatever') and just test for length. Of course, it removes the small amount of information that distinguished between a variable not existing in the scope and it having a null length, but that is a price I'm willing to pay to avoid all of the If IsDefined("input.whatever") code I used to have to write.

Thirdly, I wanted to support custom getters that would automatically overload the base method but only for certain attributes. If there was a custom getter for User.get('Age'), I wanted to use that, but if there wasn't one for User.get('FirstName') I wanted to just use the generic method.

Fourthly I wanted to make sure that only my bean needed to know whether or not there was a custom getter for a given property. If I have to call User.getAge() (for the custom method) and User.get('FirstName') (for the generic one) I'm exposing more details of my beans internals than I really should.

So, how does it work? Have a look at the code and I think you'll see it is pretty obvious . . .

<cffunction name="get" returntype="any" access="public" output="false" hint="I am a generic getter that handles getting of all attributes. I confirm they are gettable, try to use a custom method, and if that fails, I just ask hardget() to get the attribute value directly.">
   <cfargument name="AttributeName" type="string" required="yes" displayname="get" hint="The name of the attribute to get">
   <cfset var Local = StructNew()>
   <cftry>
   <cfscript>
      If (ListFindNoCase(variables.gettableAttributes,arguments.AttributeName) OR variables.gettableAttributes EQ "*")
      {
         // The attribute is "gettable" (either because it is in the list or because all attributes are gettable (*))
         if (structKeyExists(variables,"get#arguments.AttributeName#"))
         {
            // If there is a custom getter, use it
            Local.ReturnValue = evaluate("variables.get#arguments.AttributeName#()");
         }
         Else
         {
            // Otherwise just pull the attribute using private "hard get"
            Local.ReturnValue = hardGet(arguments.AttributeName);
         };
      }
      Else
      {
         // The attribute is not "gettable"
         If (variables.ReturnNullforNonExistantAttributes)
            {Local.ReturnValue = "INVALID_ATTRIBUTENAME(#arguments.AttributeName#)";}
         Else
            {Local.ReturnValue = "";};
      };
   </cfscript>
      <cfcatch>
         <cfif variables.ReturnNullforNonExistantAttributes>
            <cfset Local.ReturnValue = "">
         <cfelse>
            <cfset Local.ReturnValue = "NON_EXISTANT_ATTRIBUTENAME(#arguments.AttributeName#)">
         </cfif>
      </cfcatch>
   </cftry>
   <cfreturn Local.ReturnValue>
</cffunction>

Firstly it checks to see if it is allowed to get the attribute or not (thus removing the biggest issue I usually have with generic getters that just let ALL of their properties hang out!). If so, it looks to see if there is a custom get#AttributeName#() method. If so, it calls that and returns that value. If not, it uses a special "hardget()" function. This is responsible for pulling the value of a given property from the array of structures it is stored in for the current iterator record. It encapsulates knowledge of the actual data structure within the variables scope and is often used by the custom getters (I'll show one of those in the next post) which need to actually get property values and then operate on them to return a particular attribute (e.g. hard getting a Date of Birth to return a users calculated Age).

You also see that if the attribute isn't gettable or the method (or its sub method) fails, it either returns a null value or a meaningful string. I'm not happy with how this works, but as I'm not sure what problems I'll need to solve, I'll just leave this alone until it becomes a problem and will refactor it from there.

I have found this a boon in prototyping applications, but even in a live app, it removes the need for "dumb" getters, supports information hiding and still allows for seamless addition of custom getters where business rules become necessary. I certainly couldn't have finished my last two projects in the timeframe I did without this and the iterating business object I've been talking about for the last couple of weeks.

Comments
Ahhh now I see the light! A brilliant way to implement custom getters and setters Peter, just brilliat! I will definitely be making these changes to my base class first thing tomorrow. I can see this being very useful for those special calculated properties. For example, like the ones you have given on wanting an Age from a Date of Birth field in the db or a calculated Price. Best to have the object take care of that for you, rather than the database or your display file. One thing I do want to help out on is I know using evaluate() is strongly suggested you don't. If the example you have above is in a CFC that is used as a base class you can use the cfinvoke method. I love it as I get around not having to use the evaluate() method. For example, in my base classes validation, sometimes I might need a custom method in my object to validate my form data. So I needed a way to call a function (lets use a real example: isRegistered() in my user object) from my base class. The user.cfc object inherits all the methods from base.cfc. In base.cfc I would just do the following to avoid the evaluate:

<cfinvoke method="#Variables.stValidate[arguments.theProp][Arguments.valNum].data#" value="#Arguments.theVal#" returnvariable="status">

Its a long variable in the method attribute so I apologize for the complication but I just have a function called addValidation() that adds 1 or more validation rules per object property so its a struct of arrays. The .data property is the 4th parameter of the addValidation() function which in this case is the name of the custom function I want to be calld during validation. So its really doing this in my base object (the isRegistered function is in my user object);

<cfinvoke method="isRegistered" value="#Arguments.theVal#" returnvariable="status">

Great way to get around not having to use evaluate(). I'll be implementing some of the things you have in this function tomorrow. Thanks again Peter. Its starting to make much more sense now. Take it easy.
# Posted By Javier Julio | 9/17/06 8:37 PM
Hi Javier,

Glad you like it! I'm still not convinced that evaluate is as bad as people suggest - I'll do some performance testing some time to check it out. Also, as you'll see, I'm using cfscript which doesn't allow for cfinvoke without creating a seperate method or function call. I did look at cfinvoke and understand why others would use it. Some time one of us will have to sit down and performance test the cfinvoke vs. the evaluate. I definitely don't think evaluate should be used where there is a simple alternative (evaluate(form.#FieldName#) can be replaced quite easily with form[FieldName]), but I'm still not sold that evaluate is evil. In fact, when I get a chance I'll do some testing. Look out for a "Evaluate() Rules" post some time soon :->
# Posted By Peter Bell | 9/18/06 6:14 AM
Peter,
Just read your article on Encapsulating recordsets and wanted to let you know what a great read I found it to be. I was just wondering if you have the code from the article posted anywhere? Again, great article!
# Posted By Dan | 10/30/06 2:05 PM
Hi Dan,

Cool - glad it is out. I will post the code tonight or tomorrow - with some updates to reflect newest thinking.
# Posted By Peter Bell | 10/30/06 2:11 PM
<a href=http://www.tbcgold.com>buy wow gold</a>
<a href=http://www.tbcgold.com>World of warcraft gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>buy wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>cheap wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>buy cheap wow gold</a>
<a href=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...>gold wow</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>dofus kamas</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>kamas dofus</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>dofus achat kamas</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>acheter kamas dofus</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>dofus kamas gratuit</a>
<a href=http://www.tbcgold.com/dofus-gold-10.html>achat dofus kamas</a>

<a href=http://www.dofushq.com>dofus kamas</a>
<a href=http://www.dofushq.com>dofus gold</a>

<a href=http://www.wowgold-de.com>wow gold</a>
<a href=http://www.wowgold-de.com>wow gold kaufen</a>
# Posted By aaaa | 12/7/07 2:17 PM
[URL=http://www.tbcgold.com]buy wow gold[/URL]
[URL=http://www.tbcgold.com]World of warcraft gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]buy wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]cheap wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]buy cheap wow gold[/URL]
[URL=http://www.tbcgold.com/world-of-warcraft-eur-gold-3.html" target="_blank">http://www.tbcgold.com/world-of-warcraft-eur-gold-...]gold wow[/URL]
[url=http://www.tbcgold.com/dofus-gold-10.html]dofus kamas[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]kamas dofus[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]dofus achat kamas[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]acheter kamas dofus[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]dofus kamas gratuit[/url]
[url=http://www.tbcgold.com/dofus-gold-10.html]achat dofus kamas[/url]

[URL=http://www.dofushq.com]dofus kamas[/URL]
[URL=http://www.dofushq.com]dofus gold[/URL]

[URL=http://www.wowgold-de.com]wow gold[/URL]
[URL=http://www.wowgold-de.com]wow gold kaufen[/URL]
# Posted By aaa | 12/7/07 2:19 PM
<a href=http://www.dofusmax.com>achat dofus kamas</a>
<a href=http://www.srogold.com/rs2-gold-1.html" target="_blank">http://www.srogold.com/rs2-gold-1.html>runescape gold</a>
<a href=http://www.srogold.com>cheap wow gold</a>
<a href=http://www.srogold.com/the-lord-of-the-ring-us-gold-29.html>lotro gold</a>
<a href=http://www.srogold.com/ever-quest-2-gold-1257.html" target="_blank">http://www.srogold.com/ever-quest-2-gold-1257.html...>eq2 gold</a>
<a href=http://www.srogold.com/maple-story-eu-gold-173.html" target="_blank">http://www.srogold.com/maple-story-eu-gold-173.htm...>maplestory mesos</a>
<a href=http://www.dofusmax.com>dofus gold</a>
<a href=http://www.srogold.com/silkroad-online-gold-27.html>silkroad gold</a>
<a href=http://www.srogold.com/rs2-gold-1.html" target="_blank">http://www.srogold.com/rs2-gold-1.html>runescape money</a>
<a href=http://www.srogold.com>wow gold</a>
<a href=http://www.srogold.com/the-lord-of-the-ring-us-gold-29.html>lotr gold</a>
<a href=http://www.srogold.com/maple-story-us-gold-20.html>maple story mesos</a>
<a href=http://www.dofusmax.com>dofus kamas</a>
<a href=http://www.srogold.com/rs2-gold-1.html" target="_blank">http://www.srogold.com/rs2-gold-1.html>runescape account</a>
<a href=http://www.srogold.com>buy wow gold</a>
<a href=http://www.dofusmax.com>kamas dofus</a>
<a href=http://www.srogold.com>buy cheap wow gold</a>
<a href=http://www.dofusmax.com>dofus achat kamas</a>
<a href=http://www.srogold.com>world of warcraft gold</a>
<a href=http://www.dofusmax.com>acheter kamas dofus</a>
<a href=http://www.srogold.com>gold wow</a>
<a href=http://www.dofusmax.com>dofus kamas gratuit</a>
<a href=http://www.srogold.com/fly-for-fun-gold-1330.html>flyff penya</a>
# Posted By nuan | 1/5/08 4:11 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.005.