Create Iterating Domain Objects Using Composition

2007 May 10
by Paul Marcotte
Over the past month, I've worked on some projects that required special data handling and calculated values. I considered using Peter Bell's Iterating Business Object, but instead decided to build an Iterator that can be composed into my domain objects. I'm a big fan of using "composition over inheritance", so I saw this as a prefect opportunity to add iteration to my domain objects with a minimum impact.Iterator provides methods to load ColdFusion queries and traverse records. For instance, if you use constructor injection, your init() method would accept Itertator as an argument. You would then store it in variables scope and add delegate methods to utilize Iterator in your domain object. The following examples assume you are using either ColdSpring or LightWire to inject your object dependencies and that your maintain a reference to either as Application.beanFactory". Here is a snippet of code from a ColdSpring XML bean definition. I currently place Iterator in a generic "util" package, but you could place the file anywhere you like. <bean id="Product" class="model.product.Product" singleton="false">
   <constructor-arg name="iterator">
      <ref bean="Iterator" />
   </constructor-arg>
</bean>
   
<bean id="Iterator" class="util.Iterator" singleton="false" />
And the Product init() method would be: <cffunction name="init" access="public" returntype="model.product.Product" output="false">
   <cfargument name="iterator" type="Iterator" required="true" />
   <cfset variables.iterator = arguments.iterator />
   <cfreturn this />
</cffunction>
The minimum delegate methods required to use Iterator are loadQuery(), hasNext() and next() (rewind() is optional but recommended). Here are the example delegate methods: <cffunction name="loadQuery" access="public" returntype="void" output="false">
   <cfargument name="rs" type="query" required="true">
   <cfargument name="maxRecordsPerPage" type="numeric" required="false">
   <cfset variables.iterator.loadQuery(argumentCollection=arguments) />
</cffunction>

<cffunction name="hasNext" access="public" output="false" returntype="boolean">
   <cfreturn variables.iterator.hasNext() />
</cffunction>
   
<cffunction name="next" access="public" output="false" returntype="void">
   <cfif variables.iterator.hasNext()>
      <cfset variables.instance = variables.iterator.next() />
   </cfif>
</cffunction>
   
<cffunction name="rewind" access="public" output="false" returntype="void">
   <cfset variables.iterator.rewind() />
</cffunction>
How it works The example delegate function next() provides insight into how to use Iterator. Assuming you maintain your domain object properties in variables.instance, calling next() will replace variables.instance with the next record from the loaded query. Let's say you have a list page for a family of "Products" for a given "Category". To display the Product list, you might make a method call to your Service Layer to retrieve a query result and subsequently loop over the query to display the Products. Example using a query: <cfset productList = Application.beanFactory.getBean("ProductService").getProductsByCategory(CategoryId) />

<cfloop query="productList">
   {display code here}
</cfloop>
Using an iterating domain object, you would first create an object instance, then load the query into the domain object. Example using Iterator: <cfset products = Application.beanFactory.getBean("Product") />
<cfset productList = Application.beanFactory.getBean("ProductService").getProductsByCategory(CategoryId) />
<cfset products.loadQuery(productList) />

<cfloop condition="#products.hasNext()#">
   <cfset products.next() />
   {display code here}
</cfloop>
At a glance one might say, "well, the second example has more lines of code." True, but if you has ever had to display totals or other calculated values in your view, you would need to create variables in your views and manage the setting of those variables. To reduce the amount of logic in your views, any calculated values can be encapsulated in the iterating domain object. This facilitates better cohesion and reusability in your code. If you are faced with some business problems that would benefit from using iteration, try Iterator out. [UPDATE] The comments for this entry discuss why the example above is flawed. After a couple of stabs, I got it right. If you don't care to read the entire dialogue, note that you can use an Iterator in place of an array of objects when your objects have smart getters. Or, if you want to use a "rich object map" in place of a mix of objects and queries. An example that better illustrates the use of Iterator follows: Imagine you have a Customer that may have multiple Addresses (Billing, Shipping, HQ). When your Customer logs in to your web site, you persist the Customer object in Session scope. Instead of having an array of Address objects (or a query of the Customer addresses) in your Customer object called "addressList", you could use an Iterating Address object. To display the address list, you would loop over the condition Customer.getAddressList().hasNext().