-
Notifications
You must be signed in to change notification settings - Fork 24
Introduction to Beans
- Introduction
- Bean Best Practices
- Mach-II Bean Support
- New in 1.8: Inner-Beans (Fruitier! More Musical!)
- Resources
Mach-II introduced built in support of beans in version 1.0.7. This article looks at what beans are and how they should be used. In class-based object orientation, the type employed by both Java and CFCs, the application is modeled as a series of classes. Classes are an encapsulation of both data and methods. A bean is simply a specialized kind of object, one that maintains strict rules for the accessing of data. If the name, bean, seems an odd choice for a programming construct, the blame must be layed on that period of time shortly after Java became popular when all things Java-related were given coffee-related names. The idea of beans comes from the Java world, as does the odd name.
Beans are primarily carrier objects, used for passing encapsulated data between application layers (model-view-controller or architectural tiers). They typically contain minimal business logic (if any), and they have simple, consistent interfaces.
For a class to be considered a bean, it must have methods for getting
and setting the values of its data members. Bean getters and setters
(also called accessors) are formalized so that for each data member,
xyz
, there exists a getXyz()
and a setXyz()
method.
Here, for example, is a bean CFC with a single data member, taxRate,
along with the requisite accessors:
<cfcomponent>
<cfset variables.instance = StructNew() />
<cfset variables.instance.taxRate = 0 />
<cffunction name="getTaxRate" access="public" returntype="numeric" output="false">
<cfreturn variables.instance.taxRate />
</cffunction>
<cffunction name="setTaxRate" access="private" returntype="void" output="false">
<cfargument name="taxRate" type="numeric" required="true" />
<cfset variables.instance.taxRate = arguments.taxRate />
</cffunction>
</cfcomponent>
As you can see from this example, the access attributes for getters and setters can be determined by the bean writer. In the case of bean CFCs, the possible values would be public, private, package or remote. Regardless of the access value, the data member (taxRate in our example) should be private - which we achieve by assigning it to the variables scope. This ensures that the only way to gain access to the data is by use of an accessor. Aside from this rule, there are other best practices that should be adhered to for consistency. These include:
- Bean data members/properties should begin with a lower case letter and should use mixedcased format. Examples of properly named properties include taxRate, length, and companyName.
- Setters should accept a single argument bearing the same name and type as the data member itself. See the example above for an illustration of this.
- Getters accept no arguments since their job consists simply of returning a data member's value.
- Beans may have other beans as data members. For example, a Company bean might have an Address bean as a data member.
- All bean CFCs should have an
init()
function that has no required arguments and returns the bean itself (thethis
scope). Theinit()
function may accept arguments, but they should not be required. The init() function should use setters to set any data passed toinit()
in the bean. - All bean data should be stored in variables.instance or like structure. This is facilitate getting snapshots of the data by returning variables.instance. If the bean data was just stored in the variables scope, the snapshot would include all of the bean's methods in addition to the data members.
- All access to data members - even by other non-accessor methods
within a bean - should be done by means of an accessor method. Here
is a method,
computeTax()
, that accepts a Product bean and uses its owngetTaxRate()
method to compute the appropriate tax:
<cffunction name="computeTax" access="public" returntype="numeric" output="false">
<cfargument name="product" type="Product" required="true" />
<cfreturn arguments.product.getPrice() * getTaxRate() />
</cffunction>
Methods other than accessors may begin with get and set. We could, for
example, have renamed computeTax()
to getTax()
. Even with
the name change, the method is not a "getter" as it does not follow the
requisite format.
While we consider the above to be essential to writing well-formed
beans, we only (highly) recommend the practice of grouping getters and
setters for a single data member together in code. This would mean that
getTaxRate()
and setTaxRate()
should be grouped together in
code as opposed to grouping all getters together and all setters
together.
Why the call for rigor in consistency in writing beans? Consistency in code saves developers valuable time and effort. In the case of beans, a consistent approach ensures that you and others will be able to concentrate on the important task at hand. Put another way, it saves brain cycles (similar to CPU cycles!) for more important work. See the resources section at the end of this article for tools that can generate beans for you (saving you a lot of typing).
<cfcomponent displayName="Address" hint="An address bean.">
<!---
PROPERTIES
--->
<cfset variables.instance = StructNew() />
<!---
INITIALIZATION / CONFIGURATION
--->
<cffunction name="init" access="public" returntype="Address" output="false">
<cfargument name="street" type="string" required="false" default="" />
<cfargument name="city" type="string" required="false" default="" />
<cfargument name="state" type="string" required="false" default="" />
<cfargument name="postalCode" type="string" required="false" default="" />
<cfset setStreet(arguments.street) />
<cfset setCity(arguments.city) />
<cfset setState(arguments.state) />
<cfset setPostalCode(arguments.postalCode) />
<cfreturn this />
</cffunction>
<!---
GETTERS/SETTERS
--->
<cffunction name="getStreet" access="public" returntype="string" output="false">
<cfreturn variables.instance.street />
</cffunction>
<cffunction name="setStreet" access="public" returntype="void" output="false">
<cfargument name="street" type="string" required="true" />
<cfset variables.instance.street = arguments.street />
</cffunction>
<cffunction name="getCity" access="public" returntype="string" output="false">
<cfreturn variables.instance.city />
</cffunction>
<cffunction name="setCity" access="public" returntype="void" output="false">
<cfargument name="city" type="string" required="true" />
<cfset variables.instance.city = arguments.city />
</cffunction>
<cffunction name="getState" access="public" returntype="string" output="false">
<cfreturn variables.instance.state />
</cffunction>
<cffunction name="setState" access="public" returntype="void" output="false">
<cfargument name="state" type="string" required="true" />
<cfset variables.instance.state = arguments.state />
</cffunction>
<cffunction name="getPostalCode" access="public" returntype="string" output="false">
<cfreturn variables.instance.postalCode />
</cffunction>
<cffunction name="setPostalCode" access="public" returntype="void" output="false">
<cfargument name="postalCode" type="string" required="true" />
<cfset variables.instance.postalCode = arguments.postalCode />
</cffunction>
</cfcomponent>
Mach-II framework similarly saves you time and effort by supporting bean
creation and population. This is accomplished with a new XML tag,
<event-bean>
. This tag, a subelement of the <event-handler>
tag, has
the following attributes:
-
name
- the name of the bean to be created -
type
- the CFC bean to be used -
fields
- the event fields to be used to populate the bean's data members (optional). -
reinit
- a boolean whether or not to reinit the bean if a bean already exists in theEvent
with the value specified in thename
attribute. Defaults totrue
if not specified.
Suppose that we have a bean CFC, Address. The properties for this bean might be street, city, state, and postalCode. Since it is a well-formed bean, we must have a getStreet ()
, setStreet()
, getCity()
, setCity()
, getState()
, setState()
, getPostalCode()
, and setPostalCode()
.
Let's further suppose that we have a form on a web page that asks for
this information. When the form is submitted to our Mach-II application,
we wish to create a new Address bean and populate it with information
from the form. The resulting bean should be placed in the event
arguments. We could, of course, write code in a listener to create the
bean and populate its data members, but the <event-bean>
tag makes it
even simpler, so long as the form field names match those of our bean:
<event-bean name="address" type="model.Address" fields="street,city,state,postalCode" />
Mach-II will create a bean of type, model.Address, and will call the setters for each of the data members specified, using the matching event variables as arguments for these setters.
Let's say that we have another bean, Registrant, that has properties of firstName, lastName, and address. While the data type of the first two properties are strings, address points to an Address bean. Here's how our XML would appear:
<event-handler event="register" access="public">
<event-bean name="address" type="model.Address" fields="street,city,state,postalCode" />
<event-bean name="registrant" type="model.Registrant" fields="firstName,lastName,address" />
... more commands ...
</event-handler>
The variables, firstName and lastName, will have already been placed in the event's argument collection. Once the bean, address, is created (through the use of an <event-bean>
), it will now be accessible to be used in the creation of the bean, registrant. The register event will now have two beans, address and registrant, which used in business logic as encapsulated units, rather than a loose collection of fields.
Also, the <event-bean>
tag can populate a bean using it's init()
function. By not specifying the fields attribute, when the bean is created the event arguments will be passed as arguments to the bean's init()
function. This allows us to have the framework populate the bean with a single method call and, optionally, place additional initialization logic in the bean.
Beans are a simple but powerful tool, but their power depends on the consistency with which they are used. This article shows the proper use of beans and explains how Mach-II makes the creation and use of beans easier still.
With the release of version 1.8 you can now add inner beans to your event-bean
declarations. Inner beans are simply beans-within-beans; for example, you could have a user
bean that has an inner address
bean associated with it:
<cfcomponent displayName="User">
. . .
<cffunction name="setAddress" access="public" returnType="void" output="false">
<cfargument name="address" type="Address" required="true" />
<cfset variables.instance.address = arguments.address />
</cffunction>
<cffunction name="getAddress" access="public" returnType="Address" output="false">
<cfreturn variables.instance.address />
</cffunction>
</cfcomponent>
<cfcomponent displayName="Address">
. . .
<cffunction name="setStreet" access="public" returnType="void" output="false">
<cfargument name="street" type="string" required="true" />
<cfset variables.instance.street = arguments.street />
</cffunction>
<cffunction name="getStreet" access="public" returnType="string" output="false">
<cfreturn variables.instance.street />
</cffunction>
</cfcomponent>
As you can see we have an address
bean that holds information relevant to an address, such as city
and postal code
; we also have the user
bean that holds a reference to an address
bean. To wire this up in the Mach-II configuration file, we would use event-bean
and inner-bean
:
<event-bean name="user" type="project.model.UserModel" fields="firstName,lastName,email,password">
<inner-bean name="address" prefix="address" fields="street,city,state,country,postalCode"/>
</event-bean>
Important note: In order for the bean to be correctly populated, you must make sure the event
object references address properties by dot notation. For example, in your HTML form, you must do the following:
<input type="text" name="address.street">
In version 1.8 the inner-bean
tag can be used recursively. What this means is that inner-bean
s may themselves have inner-beans
. To build on the earlier example, what if each address
object had a country
object that contained the country's name and top-level domain abbreviation (TLD)? The Mach-II configuration XML would look like this:
<event-bean name="user" type="project.model.UserModel" fields="firstName,lastName,email,password">
<inner-bean name="address" prefix="address" fields="street,city,state,postalCode">
<inner-bean name="country" prefix="address.country" fields="name,tld"/>
</inner-bean>
</event-bean>
Notice how the prefix needs to grow with the depth of the inner-bean
declaration. The corresponding HTML form that would send in the TLD information would look like this:
<input type="text" name="address.country.tld">
This convention follows the convention used by the Mach-II Form Tag Library. Read more there to find out how Mach-II offers complete round trip data binding with the form tag library and the event-bean support.
The autopopulate
attribute helps to remove all the tedious inner-bean
configuration from the XML file. If your event
object has its form elements referenced correctly (by the aforementioned dot notation) then the autopopulate
attribute will scan every submitted form element and attempt to put it into your object hierarchy correctly. If we were using the previous section's object configuration (user
, address
, and country
) we would simply write the following in our Mach-II config file:
<event-bean name="user" type="project.model.UserModel" autopopulate="true"/>
You can also use the field tag to specify specific field you want to ignore or to provide a value for using Mach II's Expression Language (EL). You might want to ignore a field to prevent a user from adding some data to a bean via a form field or url var. You can do this by using the field tag below.
<event-bean name="user" type="project.model.UserModel" autopopulate="true">
<field name="id" ignore="true" />
<field name="birthdate" value="${event.birthmonth}/${event.birthday}/${event.birthyear}" />
</event-bean>
The second field tag in the above example shows how you can use Mach II EL to concatenate several form fields together in put that in the user bean. In addition to data from the event object you can also pull information from the the Mach II properties scope. You can also use the field tag inside an inner-bean tag.
Adobe CF 9 includes support Hibernate which is a java based object-relational mapping framework. ORM libraries are design to allow you to map your beans an their relationships to other beans to a set of database tables and fields. When using the ORM support in CF 9 you need to ensure you are taking into account that by default all the properties in your CFC will be set to a Java null by default. This is done so that you can indicate to Hibernate that you want the database field to contain a null if the property isn't set to something else. CF doesn't always handle nulls the best so it is recommended that you use the cfproperty default attributes like the example below so that you have an actual value returned when accessing your properties.
<cfcomponent output="false" displayname="user" persistent="true" table="users"
entityname="appbooster.model.user.user">
<cfproperty name="id" type="numeric" generator="identity" unSavedValue="0" />
<cfproperty name="firstname" type="string" default="" />
<cfproperty name="lastname" type="string" default="" />
<cfproperty name="rating" type="numeric" default="0" />
</cfcomponent>
There are several tools that can generate beans for you in variety of ways - such as introspecting a database table or by inputting a shorthand template of bean members. Here is short and not so comprehensive list of tools:
- cfcPowerTools - free and open source
- Illudium PU-36 Code Generator - free and open source (has flex interface and generates cfcs from a database)
- Rooibos Bean Generator - free to use javascript application (generates bean via a shorthand template entered by the developer)
- A comprehensive list of code generators from Brian Rinaldi's Open Source list