Object Oriented Coldfusion : 6 : The Bean Object
Posted on September 13, 2007
Now that we can easily retrieve and display record sets using the Gateway Object, it’s time to work with a specific record. Using a Bean to manage the properties of a single data record we can maintain strict control over a record as it is used throughout one or more applications.
One bean, one . . . instance of associated data
Is it getting better
Or do you feel the same
Will it make it easier on you now
You got [some bean] to blame
(with apologies to U2).
The name “bean” comes from the Java programming language (java > coffee > coffee bean, get it?). According to Wikipedia, a bean object is “used to encapsulate many objects into a single object (the bean), so that the bean can be passed around rather than the individual objects.”
A bean in Coldfusion is created using a single Component (CFC) to encapsulate
a single “record” of data. (As opposed to a Gateway Object, which returns record sets.)
In this example, the data in a bean will be read from and recorded to a single record in a single database table.
Database Table: Categories
CATEGORY_ID | CATEGORY_LABEL |
---|---|
1 | Programmer |
2 | Corporate |
3 | Real Estate |
One bean != one database table record
In many applications, the “single record” of data stored in a bean could be compiled from reading multiple tables or other sources and later recorded to multiple destinations.
What makes a bean a bean?
From the Wikipedia article, the required conventions [of a bean] are:
- The class must have a no-argument constructor
- Its properties must be accessible using get, set and other methods (so called accessor methods) following a standard naming convention
- The class should be serializable (able to persistently save and restore its state)
- It should not contain any required event-handling methods
Let’s take a look at this Category bean (CFC) and see where a Coldfusion bean deviates from a Java bean:
/cfc/mySite/categories/Category.cfc
<cfcomponent name="Category" hint="This is a Category Bean.">
<cfset variables.instance.CategoryID = 0 />
<cfset variables.instance.Label = "" />
<cffunction name="init" displayname="init" hint="Bean for CF_CATEGORIES" access="public" output="false" returntype="Category">
<cfargument name="CategoryID" type="numeric" required="false" default="0" hint="CATEGORY_ID" />
<cfargument name="Label" type="string" required="false" default="" hint="CATEGORY_LABEL" />
<cfset variables.instance = structNew() />
<cfset setCategoryID(arguments.categoryID) />
<cfset setLabel(arguments.Label) />
<cfreturn this />
</cffunction>
<cffunction name="getCategoryID" access="public" hint="Getter for CategoryID" output="false" returnType="numeric">
<cfreturn variables.instance.CategoryID />
</cffunction>
<cffunction name="setCategoryID" access="private" hint="Setter for CategoryID" output="false" returnType="void">
<cfargument name="CategoryID" hint="" required="yes" type="numeric" />
<cfset variables.instance.CategoryID = arguments.CategoryID />
</cffunction>
<cffunction name="getLabel" access="public" hint="Getter for Label" output="false" returnType="string">
<cfreturn variables.instance.Label />
</cffunction>
<cffunction name="setLabel" access="private" hint="Setter for Label" output="false" returnType="void">
<cfargument name="Label" hint="" required="yes" type="string" />
<cfset variables.instance.Label = arguments.Label />
</cffunction>
</cfcomponent>
Bean convention #1: a no-argument constructor
As discussed in part 1 of this primer, init()
is the constructor
method of the bean. While the init() method has arguments, they are all set to required=”false”, which essentially makes this function a “no-argument constructor”.
Creating an instace of the Category bean
<cfset Category = createObject("component", "cfc.mySite.categories.Category").init() />
Bean convention #2: Standard access to its properties
The constructor method init()
creates a struct variable named “instance” in the variables scope of the CFC. The variable “variables.instance” is local to the Component, and therefore is not directly accessible to any external request.
The keys of this struct are comprised of the bean’s properties.
The standard convention for changing the value of a property is to use a set_Property_()
function. This type of Bean functions is commonly called a setter
. Its more technical term is mutator
.
The standard convention for reading a property is to use a get_Property_()
function. This type of Bean function is commonly called a getter
. Its more technical term is accessor
.
Alternate naming convetions for getters that return boolean values are names like is_Property_()
and has_Property_()
.
Bean convention #3: Serializable objects
From the Sun Java docs: “Object Serialization supports the encoding of objects and the objects reachable from them, into a stream of bytes.”
Through ColdfusionMX 7.0.2, an instance of a Coldfusion Component (CFC) cannot be serialized, although it is possible to mimic this through programming. Beginning with ColdfusionMX 8 (Scorpio), a CFC can be serialized.
Bean convention #4: No required event-handling methods
Beans in Coldfusion applications don’t handle events, they are data containers.
Why variables.instance?
Jeff Chastain beat me to this subject by a few months. Check out his post for 4 reasons to use variables.instance. I’ll add one more that Dave Shuck brought up at our last DFW CFUG meeting.
In Anatomy of an Object.cfc, we discovered what is in the variables scope
of a CFC:
functions
- each function name is a key in the variables structthis
- an instance of the component itself- variables in the
variables scope
are accessible to all functions in the CFC
If you were to have a function “foo()”
SomeBean.cfc
<cffunction name="foo">
<cfreturn "Hello." />
<cffunction>
and a bean property named “foo”,
SomeBean.cfc
<cffunction name="setFoo">
<cfargument name="foo" />
<cfset variables.foo = arguments.foo />
</cffunction>
then variables.foo
would first be defined as the function foo(). When you then call setFoo( _x_ )
, you’ve just redefined variables.foo
as the value of x
, eliminating the function foo().
By using variables.instance, you’re ensuring that all the properties of a bean are insulated from the rest of the object’s variables scope
.
Two types of beans
The type of bean you implement depends on the needs of your application.
Read-only Bean
In the example Category.cfc above, all setter
functions are specified as private
, so no external process can call them directly. To change the values of the properties of this bean, you must send argument values through the init() method.
Creating and populating an instance of a read-only Category bean
<cfset Category = createObject("component", "cfc.mySite.categories.Category").init() />
<cfoutput>
[ #Category.getCategoryID()# ] #Category.getLabel()#
</cfoutput>
<!--- [ 0 ] --->
<cfset Category.init( CategoryID = 1 ) />
<cfoutput>
[ #Category.getCategoryID()# ] #Category.getLabel()#
</cfoutput>
<!--- [ 1 ] --->
<cfset Category.init( CategoryID = 2, Label = "Hello" ) />
<cfoutput>
[ #Category.getCategoryID()# ] #Category.getLabel()#
</cfoutput>
<!--- [ 2 ] Hello --->
When you call init()
in this manner, values are passed to the specified arguments and then the setter
functions are called. Each setter takes a single argument which then updates its associated property of “variables.instance”.
The getter
functions will now return the updated property values.
Read / Write Bean
A read / write bean allows the setter
methods to be public
. When a bean is written like this, you can change the value of any bean property outside of the init()
method and at any time.
Changes to Category.cfc
<cffunction name="setCategoryID" access="public" hint="Setter for CategoryID" output="false" returnType="void">
<cffunction name="setLabel" access="public" hint="Setter for Label" output="false" returnType="void">
Creating and populating an instance of a read/write Category bean
<cfset Category = createObject("component", "cfc.mySite.categories.Category").init() />
<cfoutput>[ #Category.getCategoryID()# ] #Category.getLabel()#</cfoutput>
<!--- [ 0 ] --->
<cfset Category.setCategoryID( 1 ) />
<cfoutput>[ #Category.getCategoryID()# ] #Category.getLabel()#</cfoutput>
<!--- [ 1 ] --->
<cfset Category.setCategoryID( 2 ) />
<cfset Category.setLabel( "Hello" ) />
<cfoutput>[ #Category.getCategoryID()# ] #Category.getLabel()#</cfoutput>
<!--- [ 2 ] Hello --->
<cfset Category.setLabel( "Goodbye" ) />
<cfoutput>[ #Category.getCategoryID()# ] #Category.getLabel()#</cfoutput>
<!--- [ 2 ] Goodbye --->
How do we interact with a datasource?
We now have an object that represents a single data “record”. While we can get a bean’s data from any source, we need a way to manage how a bean talks to that source. Next we’ll go over the most common way to abstract that interaction by creating a Data Access Object (DAO).
Adrian J. Moreno
Adrian is a CTO and solution architect specializing in software modernization. More information