Frank Fusion

Saturday, 26 April 2008

ColdFusion Encapsulation Gotcha

Good object design dictates that we encapsulate our objects, hiding direct access to object properties from outside code. Access to the values of these properties is often granted to outside code with the use of 'getter' methods.

The following, useless, component appears to encapsulate its 'foo' property using a getter method but, as we shall see shortly, does nothing of the kind:

<cfcomponent output="false" displayname="foobar">
<cfset variables.foo = StructNew()>
<cfset variables.foo.bar = "Hello world">

<cffunction name="GetFoo">
<cfreturn variables.foo>
</cffunction>
</cfcomponent>

Why is that not encapsulated?

The reason the 'foo' property is not encapsulated here is that ColdFusion passes certain variable types by reference rather than value. So when we return the variables.foo structure we are actually returning a reference to the original data in memory rather than the data itself.

The following code demonstrates what effect this has:

<h1>Encapsulation gotcha example</h1>
<cfset oFoo = CreateObject('component', 'foobar')><!--- the component defined above --->

<h2> Before:</h2>
<cfdump var="#oFoo.GetFoo()#">

<cfset fooReference = oFoo.GetFoo()>
<cfset fooReference.foo = "Wayne's world, excellent">

<h2> After:</h2>
<cfdump var="#oFoo.GetFoo()#">

If the 'foo' property were properly encapsulated here, both dumps would show the same thing. However, because we have a reference to the original data in the variables 'fooReference', we can make changes to the data directly (breaking encapsulation).

What to do about it?

It is important to note here that passing variables by reference saves memory - it is certainly NOT a bad thing! However, if encapsulation is more important to you, you can use the Duplicate() method to return a reference to a copy of the data which has the effect of returning the data itself:

<cffunction name="GetFoo">
<cfreturn Duplicate(variables.foo)>
</cffunction>

Final note

ColdFusion passes queries, structures and external objects such as COM objects and CFC instances by reference.

Strings (including numeric values) and arrays are passed by value and do not need to be 'duplicated' in this way.

Here is a link to an Adobe livedocs page that talks about passing variables to and from functions in detail:

http://livedocs.adobe.com/coldfusion/7/htmldocs/wwhelp/wwhimpl/common/html/wwhelp.htm?context=ColdFusion_Documentation&file=00001008.htm

Labels: ,

5 Comments:

  • Very valid points, and something that should always be considered in one's application design. An easy thing for the uninitiated to forget.

    By Blogger Cutter, At 26 April 2008 14:53  

  • The typical way of dealing with this problem is to provide getters/setters for value types, but then provide delegate methods for reference types. For example, if you have an Array of customers, you'd write addCustomer, deleteCustomer, getCustomerCount, etc methods to expose the behaviour of the array.

    Obviously you sometimes want to return the actual array, but in that case you usually want the real array, not a copy. Of course, ColdFusion hamstrings you by having made Array a value type (for God knows what reason), but that holds for Structs, Recordsets, and CFC instances.

    By Blogger Barney Boisvert, At 26 April 2008 15:55  

  • @Barney - certainly; another part of encapsulation is of course only providing the interface that is neccessary; if outside code doesn't need access to the 'customer array', don't provide a getter method but provide 'delegates' as you say.

    If outside code *does* need access to a reference type property though, I don't believe you would mostly want to return a reference to it at all; doing so completely surrenders control of the object's state to outside code.

    By Blogger Dom, At 26 April 2008 16:07  

  • dom,

    Outside code needing to access the array is not a common need, so it really is usually a need for the actual object. If you want to iterate, for example, you don't need the array, you just need an iterator. That holds even if you need to reorder the array - you can do that with iterators as well. The standard CRUD operations (operating on the array as the persistent store, mind you), should all happen through delegates. But if you need to actually transform the array for some reason, then you need the real array. But like I said, that's the exceptional case, and delegates handle pretty much all the normal ones.

    By Blogger Barney Boisvert, At 26 April 2008 16:17  

  • @Barney

    "But if you need to actually transform the array for some reason, then you need the real array."

    That translates to, 'if you need to break encapsulation'. Either way, you won't have access to the original array because arrays are passed by value ;)

    There are cases in which outside code needs/wants read-only access to properties that of reference types. If manipulation of the data is required, then you rightly point out that delegate methods should be used.

    By Blogger Dom, At 26 April 2008 16:34  

Post a Comment



<< Home