"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:
- They're a direct encapsulation violation, as you have to know implementation details of the CFC to know how to use it.
- It's based on the magic string / magic number antipattern, where strings evaluated at runtime take on API, not data, significance.
- 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:
<cfcomponent extends="AccessorHack">
<cfproperty access="public" name="name" />
<cfproperty access="private" name"privateProperty" />
</cfcomponent>
<!--- 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
- Objects are still true objects - they have defined properties. Encapsulation and API definition are maintained.
- Instead of literals (set("Name")), you work against methods.
What I don't like about it
- 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.
- 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.
- 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):
<!--- 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 <cfproperty> tag!" />
</cfif>
<!--- Beware: doesn't support named args --->
<cfif structCount(missingMethodArguments)>
<cfset variables[missingMethodName] = missingMethodArguments[1] />
<cfelse>
<cfreturn variables[missingMethodName] />
</cfif>
</cffunction>
</cfcomponent>


Interesting! I'd played with something similar a while back. But (to give you something to *really* object to) most of my class files don't actually exist - they're described as metadata in an XML config file (not the code - but rather the intent of the code), so most of my business objects, service classes, DAOs and controllers are XML plus a base class rather than physical files. For apps where most of your methods can be described declaratively it's actually a surprisingly useful approach. For apps where most classes need some custom code, the minor gains wouldn't be worth all of the downside in terms of a different approach to programming, not being able to use automated documentation tools and the like.
Anyhow, the reason I didn't end up going with the cfproperty approach is that it actually requires a physical class file for each bean and while I briefly considered going back to generating them and using cfproperty, I decided in the end just to mak the XML the authoritative description of the applications.
For people who actually have their class files, I think this is definitely an interesting approach!
<cfloop list="#PropertyNameList#" index="PropertyName">
. . .
</cfloop>
in my default screens and processors. I use cfscript which doesn't support cfinvoke, so get(PropertyName); works well, but get#PropertyName#() is currently not supported in ColdFusion unless I plan to evaluate() everything or give up on cfscript, so I actually find the generic syntax to be more flexible for very generalized stuff.
You could technically only crawl the inheritance stack until you find the property they are looking for. This would not be cacheable, as you have done with the property table, but might decrease the initial load time for the solution.
What I love about this is that if you want to customize the functionality, all you need to do is define the method. You could also define the method if you needed access speed.