Skip to content

Introduction to Beans

thofrey edited this page Apr 1, 2014 · 9 revisions

Table of Contents

  1. Introduction
  2. Bean Best Practices
  3. Mach-II Bean Support
  4. New in 1.8: Inner-Beans (Fruitier! More Musical!)
  5. Resources

Introduction

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.

Bean Best Practices

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 (the this scope). The init() function may accept arguments, but they should not be required. The init() function should use setters to set any data passed to init() 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 own getTaxRate() 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).

A full bean example:

    <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 Bean Support

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 the Event with the value specified in the name attribute. Defaults to true 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.

New in 1.8: Inner-Beans (Fruitier! More Musical!)

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">

Recursive Inner-Beans (or More (More (More (More Musical!))))

In version 1.8 the inner-bean tag can be used recursively. What this means is that inner-beans 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

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"/>

The field tag

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.

CF 9 ORM Notes

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>

Resources

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:

Clone this wiki locally