last edited byusericonadmin on 25-Apr-2010

Contents

Builder

The builder pattern provides a method for creating a complex object from smaller pieces in a step by step fashion. In addition to this, it allows you to change the pieces from one kind to another, so when following the same construction steps you create a different kind of object.

There are two kinds of objects involved in this pattern, a Director and a Builder.

The Builder knows how to create the individual parts required and also knows how those parts may be combined together in a valid way. The Director is responsible for knowing the steps required to create a specific kind of object. It knows the order of the steps and asks the Builder to piece the parts together.

In this diagram, the Director's construct() function contains the steps required to create the final object but asks the Builder object to actually perform those steps.

<!--- Create the builder and director --->
<cfset builder = createObject("component","Builder").init()>
<cfset director = createObject("component","Director").init()>

<!--- Tell the director to construct the end product using the builder --->
<cfset director.construct(builder)> 

<!--- Ask the builder for the end product --->
<cfset result = builder.getResult()> 

An example construct() function may have code such as

<cffunction name="construct" output="false">
    <cfargument name="builder" required="true"> 
    <cfset arguments.builder.buildPartA()>
    <cfset arguments.builder.buildPartB()>
    <cfset arguments.builder.buildPartC()>
</cffunction>

We may also have multiple kinds of builders that may all be used with the same Director object. In this case we need all of the builders to have exactly the same set of build functions.

In this diagram we have:

Within CFML The Abstract Builder does not necessarily need to be created but serves as a reference of the builder functions as well as providing an 'empty' version of each function.

Let's take a look at some examples of a Builder.

Example: Creating a fast food children's meal

In a fast food restaurant it is common to have a children's meal made up of:

We can create a Cashier that is responsible for putting all of these items together:

<cfcomponent name="Cashier" output="false">

    <cffunction name="prepareKidsMeal" output="false">
        <cfargument name="kidsMealBuilder" required="true">
        <cfset var builder = arguments.kidsMealBuilder>
        <cfset builder.addMainItem()>
        <cfset builder.addSideItem()>
        <cfset builder.addDrink()>
        <cfset builder.addToy()>
    </cffunction>

</cfcomponent>

There may be a few different types of special kids meals. For example, we may have a Robot kids meal that consists of a Robot shaped burger, fries, a blue drink and a robot toy. Another kind of meal may be a Princess kids meal that consists of a Nuggets, fries, a pink drink and a princess toy.

We can create a RobotMealBuilder and a PrincessMealBuilder that each implement the four required functions. So to create these two meals we could write:

<cfset cashier = createObject("component","Cashier").init()>
<cfset robotBuilder = createObject("component","RobotMealBuilder").init()>
<cfset princessBuilder = createObject("component","PrincessMealBuilder").init()>

<cfset cashier.prepareKidsMeal(robotBuilder)>
<cfset cashier.prepareKidsMeal(princessBuilder)>

<cfset robotMeal = robotBuilder.getMeal()>
<cfset princessMeal = princessBuilder.getMeal()>

Example: Creating a maze

Consider that you are building some software to construct a maze that is made up of rooms that are connected by doors. In this case we can create a maze builder object that knows how to create a maze then add rooms and doors.

Let's create a trivial "maze" that has the following layout:

|-------|-------|
|       |       |
|   A   #   B   |
|       |       |
|------- ---#---|
|       |       |
|   C   #   D   |
|       |       |
|-------|-------|

In this diagram we have four rooms labelled A, B, C and D.

The # symbols represent doors, so we have doors from:

We can create a MazeBuilder that knows how to create the individual parts of our maze:

Our functions are:

Let's now create a MazeGameCreator object that creates mazes:

<cfcomponent name="MazeGameCreator" output="false">

    <cffunction name="createMaze" output="false">
        <cfargument name="mazeBuilder" required="true">
        <cfset var builder = arguments.mazeBuilder>
        <cfset builder.createMaze()>
        <cfset builder.createRoom("A")>
        <cfset builder.createRoom("B")>
        <cfset builder.createRoom("C")>
        <cfset builder.createRoom("D")>
        <cfset builder.createDoor("A","B")>
        <cfset builder.createDoor("B","D")>
        <cfset builder.createDoor("C","D")>
    </cffunction>

</cfcomponent>

To use our MazeGameCreator we can write:

<!--- Create the builder and game creator --->
<cfset mazeBuilder = createObject("component","MazeBuilder").init()>
<cfset mazeGameCreator = createObject("component","MazeGameCreator").init()>

<!--- Create the maze --->
<cfset mazeGameCreator.createMaze(mazeBuilder)>

<!--- Get the final Maze from the builder --->
<cfset maze = mazeBuilder.getMaze()>

Example: Parsing complex text files (Advanced)

Consider a system that tracks all of the locations of a package throughout a journey from its origin to destination. This tracking system may not have been developed by you and you simply receive a complex data file that contains all of the relevant tracking information. This complex data file may have a complicated format as follows (indented here to improve readability):

MGS+20091204+1623
    ITS+1024123
        LOC+AU+SYD
        DTM+20091201+0903
        LOC+AU+MEL
        DTM+20091202+1125
    ITE
    ITS+8765453
        LOC+AU+SYD
        DTM+20091201+1021
        LOC+NZ+WEL
        DTM+20091203+1355
        LOC+NZ+AUK
        DTM+20091204+1123
    ITE
MGE

This is a very simplified mock file format with a similar concept to a format such as EDIFACT. In our mock example, these cryptic tags mean the following:

So we can see that this is a message with two items of data. The first item was processed at two locations; Sydney and Melbourne. The second item was processed at three locations; Sydney, Wellington and Auckland.

Creating a Message Builder

A Builder may be useful in helping us convert this file into a more usable representation. Lets create an object called an AbstractMessageBuilder.

In this example:

This AbstractMessageBuilder really just represents the functions required in a builder. We now need to create an actual builder that implements these functions. Let's create a StructMessageBuilder that extends the AbstractMessageBuilder and is designed to creates a simple struct version of the input message data.

In our StructMessageBuilder we implement each of the functions above, but we also add in an extra function getStruct() which returns the final result.

Our AbstractMessageBuilder may also have internal logic to ensure that the build steps were processed in a valid order, and perhaps throw exceptions if a valid order was not followed.

So at this point we now have:

Now suppose that our requirement changed and we needed the builder to return a different format from our simple struct. Perhaps we need it to return an XML object instead. In this case we just create different builder object that caters to this new requirement.

Let's create an XMLMessageBuilder that had extends the AbstractMessageBuilder, but instead has a getXML() function that returns an XML object.

Because these alternative builder objects all have exactly the same functions then they can easily be substituted for one another without changing the calling code that reads the actual data file.

Creating a Message Parser

Let's create a Message Parser object that will make use of our builder code

For this example we pass the input data file path and whichever builder is required to the parse() function.

<cfcomponent name="MessageParser" output="false">

    <cffunction name="parse" output="false">
        <cfargument name="messageFile" required="true">
        <cfargument name="messageBuilder" required="true">
        <cfset var builder = arguments.messageBuilder>
        <cfset var line = "">
        <cfset var lineCode = "">
        <cfset var local = {}>
        <cfloop index="line" file="#arguments.messageFile#">
            <cfset lineCode = trim(left(line,3))>
            <cfswitch expression="#lineCode#">
                <cfcase value="MGS">
                    <cfset local.date = ... Parse date from line into a date object ...>
                    <cfset builder.addMessage(local.date)>
                </cfcase>
                <cfcase value="ITE">
                    <cfset local.itemNumber = ... Parse item number from line ...>
                    <cfset builder.addItem(local.itemNumber)>
                </cfcase>
                <cfcase value="DTM">
                    <cfset local.date = ... Parse date from line into a date object ...>
                    <cfset builder.addItemDate(local.date)>
                </cfcase>
                <cfcase value="LOC">
                    <cfset local.country = ... Parse country code from line ...>
                    <cfset local.city = ... Parse city code from line ...>
                    <cfset builder.addItemLocation(local.country,local.city)>
                </cfcase>
            </cfswitch>
        </cfloop>
    </cffunction>

</cfcomponent>

To use our message parser we may write:

<!--- Create the builder and parser --->
<cfset builder = createObject("component","StructMessageBuilder").init()>
<cfset parser = createObject("component","MessageParser").init()> 

<!--- Parse the file and get the result --->
<cfset parser.parse(messageFile,builder)>
<cfset messageStruct = builder.getStruct()>

If we wanted to process the file and get an XML file, we just swap in the XMLMessageParser:

<!--- Create the builder and parser --->
<cfset builder = createObject("component","XMLMessageBuilder").init()>
<cfset parser = createObject("component","MessageParser").init()> 

<!--- Parse the file and get the result --->
<cfset parser.parse(messageFile,builder)>
<cfset messageXML = builder.getXML()>

So the MessageParser object, which is responsible for reading the input file and calling the appropriate functions on the builder, remains unchanged and independent of the resulting representation of the output.

UML diagram

Following is a UML diagram of out Message Parser and Builder objects.

References

Builder Pattern
http://en.wikipedia.org/wiki/Builder_pattern/

Builder Design Pattern
http://sourcemaking.com/design_patterns/builder/

Builder Pattern
http://www.oodesign.com/builder-pattern.html