Friday, December 18, 2009

Using onMissingMethod to Treat Properties More Like... Properties

Since I didn't come from a Java background, I'm relatively new to OOP best practices. One thing that I've grown to loathe in my short time working with objects in Hibernate is having to go through getters and setters to access my properties:


<cfset user = new User() />
<cfset user.setFirstName("Tony") />
<cfset user.setLastName("Nelson") />

<cfoutput>
My name is #user.getFirstName()# #user.getLastName()#
</cfoutput>


I'd much rather prefer true implicit getters and setters a la Groovy, ActionScript, C#, etc...


<cfset user = new User() />
<cfset user.firstName = "Tony" />
<cfset user.lastName = "Nelson" />

<cfoutput>
My name is #user.firstName# #user.lastName#
</cfoutput>


After working with jQuery a little more, I really like the convention used on their event helpers, like change() and change(fn). If you pass in an argument, it binds a new observer. If you don't pass in an argument, it triggers the event.

I was able to apply similar logic to my entities in ColdFusion with the help of onMissingMethod() inside a base class that my entities extend.


component {

public void function set(required string property, required any value) {

if (structKeyExists(this,"set#arguments.property#")) {

if (!structKeyExists(arguments,"value") || isNull(arguments.value) || (isSimpleValue(arguments.value) && arguments.value eq "")) {
evaluate("set#arguments.property#(javacast('NULL',''))");
}
else {
evaluate("set#arguments.property#(arguments.value)");
}

}

}

public any function get(required string property) {

if (structKeyExists(this,"get#arguments.property#")) {
local.value = evaluate("get#arguments.property#()");
}

if (!structKeyExists(local,"value")) {
local.value = "";
}

return local.value;

}

public any function onMissingMethod(required string missingMethodName, required struct missingMethodArguments) {

if (structIsEmpty(arguments.missingMethodArguments)) {
return get(arguments.missingMethodName);
}

set(arguments.missingMethodName,arguments.missingMethodArguments[1]);

return this;

}

}



Now if I pass in an argument, it sets the property. If I don't pass an in argument, it gets the property, leaving me with the following code:


<cfset user = new User() />
<cfset user.firstName("Tony") />
<cfset user.lastName("Nelson") />

<cfoutput>
My name is #user.firstName()# #user.lastName()#
</cfoutput>


And in case you didn't read the code, also included in the base class are generic get() and set() methods that relay calls to the actual property getters and setters in order to maintain proper encapsulation.

4 comments:

  1. Interesting, but... is it worth so much additional code and the performance tax of onmissingmethod to get you

    user.firstName("Tony")

    instead of user.setFirstName("Tony")?

    ReplyDelete
  2. @Henry,

    Yeah you could argue that its not worth it for such a small cosmetic change, but the performance hit of oMM isn't that bad (from what I've heard and since I'm performing additional logic in the get() and set() methods (setting nulls, returning empty strings), you could maybe justify the extra code behind the scenes.

    ReplyDelete
  3. I've been fooling around with Groovy a lot lately and reading up on it and it's very interesting. I think it's neat how they have implicit getter/setter methods based on calls to properties; but, I am not sure that I *prefer* that way just yet. Of course, this could just be me being unused to it.

    I happen to like the use of OnMissingMethod() to use the getXXX() and setXXX() implicitly. But that's just my personal preference.

    ReplyDelete
  4. It's funny, you asked me about this a little while back and I had said I preferred get() and set() for readability, but after working with ActionScript I'm really liking the implicit getters/setters.

    ReplyDelete