"Getters and Setters" - a different CFC approach

With an ongoing debate over me stating that generic getters and setters were a bad idea, I thought I'd take a stab at an implementation that tried to fix Peter Bells's fault with them (they're verbose) without falling into problems I had with Peter's "Generic" approach:

  1. They're a direct encapsulation violation, as you have to know implementation details of the CFC to know how to use it.
  2. It's based on the magic string / magic number antipattern, where strings evaluated at runtime take on API, not data, significance.
  3. They don't produce an API that can be used by others, as an object has no external API describing its properties.

While I was accused of zealotry, the accusation was based on the assumption that I thought forcing people to write getFoo() and setFoo() is a hard-and-fast rule applicable to every application.

It's not.

I cooked up the following bit, following an idea Mark Mandel had a while back while discussing implicit setters. I can't claim any originality - over IM last night, Mark knew exactly what I was up to before I even told him what I was doing.

The "hack" results in a property manipulation API such as this, where you use <cfproperty> tags to declare properties, and only those marked as access="public" can have accessors and writers "generated" on-the-fly:

<!--- CFC --->
<cfcomponent extends="AccessorHack">
<cfproperty access="public" name="name" />
<cfproperty access="private" name"privateProperty" />
</cfcomponent>
<cfset contact.name("Hank") />
<!--- Shows "Hank" --->
<cfoutput>#contact.name()#</cfoutput>
<!--- Tosses private property error --->
<cfset contact.privateProperty("value") />
<!--- Tosses variable not defined --->
<cfreturn contact.undefinedProperty() />

What I Like About It

  1. Objects are still true objects - they have defined properties. Encapsulation and API definition are maintained.
  2. Instead of literals (set("Name")), you work against methods.

What I don't like about it

  1. The syntax is a bit weird. The equals sign is just what I see as being assignment to a property, but setFoo() doesn't have that either.
  2. There's a runtime cost associated with getting all properties up an object's inheritance chain on the first get/set, and I'd find that unacceptable in a high-load app.
  3. There's no type-checking, as I didn't want to add that cost or write the code for it.

Conclusion

It's an interesting experiment, but not one I'll put forth as a good technique for big apps.

Code

Here's the code that was used (obviously CF8-only):

<cfcomponent name="AccessorHack">

<!--- Builds a table of properties metadata that crawls up inheritance chain. --->
<cffunction name="buildPropertyTable" output="false">
   <cfargument name="table" default="#structNew()#" />
   <cfargument name="md" default="#getMetadata(this)#" />
   
   <cfset var prop = 0 />
   
   <cfif structKeyExists(md, "properties")>
      <cfloop array="#md.properties#" index="prop">
         <cfif structKeyExists(prop, "access") and prop.access eq "public">
            <cfset table[prop.name] = prop />
         </cfif>
      </cfloop>
   </cfif>
   
   <cfif structKeyExists(md, "extends")>
      <cfset buildPropertyTable(table, getComponentMetadata(md.extends.name)) />
   </cfif>
   
   <cfreturn table />
</cffunction>

<!--- Sets / gets whatever is passed directly to / from variables, as long as it's declared "public" --->
<cffunction name="onMissingMethod" output="false">
   <cfargument name="missingMethodName" />
   <cfargument name="missingMethodArguments" />

   <!--- Build table if necessary --->
   <cfif not structKeyExists(this, "publicPropertyMetadata")>
      <cfset this.publicPropertyMetadata = buildPropertyTable() />      
   </cfif>
   
   <cfif not structKeyExists(this.publicPropertyMetadata, missingMethodName)>
      <cfthrow message="#missingMethodName# is not declared as public in a &lt;cfproperty&gt; tag!" />
   </cfif>
   
   <!--- Beware: doesn't support named args --->
   <cfif structCount(missingMethodArguments)>
      <cfset variables[missingMethodName] = missingMethodArguments[1] />
   <cfelse>
      <cfreturn variables[missingMethodName] />
   </cfif>
</cffunction>

</cfcomponent>

ActiveRecord has (bad!) consequences.

This started out as a long comment on Brian Kotek's recent post about the ActiveRecord pattern. I figure that while I've still got some Rails-ish types irked at me, I may as well go ahead and blog a bit more about why I don't like the datacentricness of the ActiveRecord approach.

Before you fire up your flame generation script, let me say this: ActiveRecord is super-productive when you need to allow someone to edit a relational model. It doesn't, however, suit the kind of OOD-backed development I do. This post is about why.

[More]

Rails' ActiveRecord, Reactor, Illudium: All backwords for OO.

So, what's with the guy who put together the MG:U stack calling this stuff backwords for something he pushes (OO)? I'm not writing this to start a flame war: I'm writing this because I'd like people to think about the tools they're using and the appropriate contexts for their use, and I chose a bit of an inflammatory title to get you to click. :)

[More]

Defending the Gateway pattern in ColdFusion

In a few recent posts, there's been discussion of the validity and purpose of what's called the "Gateway" pattern in a ColdFusion application's object model.

[More]

© 2008 Firemoss, LLC
BlogCFC was created by Raymond Camden. This blog is running version 5.8.001.