Chapter 13. The Builder Pattern

In this chapter we'll consider how to use the Builder pattern to construct objects from components. We have already seen that the Factory pattern returns one of several different subclasses, depending on the data passed in arguments to the creation methods. But suppose we don't want just a computing algorithm but a whole different user interface because of the data we need to display. A typical example might be your e-mail address book. You probably have both individual people and groups of people in your address book, and you would expect the display for the address book to change so that the People screen has places for first and last name, company, e-mail address, and phone number.

On the other hand, if you were displaying a group address page, you'd like to see the name of the group, its purpose, and a list of members and their e-mail addresses. You click on a person and get one display and on a group and get the other display. Let's assume that all e-mail addresses are kept in an object called an Address and that people and groups are derived from this base class, as shown in Figure 13-1.

Both Person and Group are derived from Address.

Figure 13-1. Both Person and Group are derived from Address.

Depending on which type of Address object we click on, we'd like to see a somewhat different display of that object's properties. This is a little more than just a Factory pattern because we aren't returning objects that are simple descendants of a base display object but totally different user interfaces made up of different combinations of display objects. The Builder pattern assembles a number of objects, such as display controls, in various ways, depending on the data. Furthermore, by using classes to represent the data and forms to represent the display, you can cleanly separate the data from the display methods into simple objects.

An Investment Tracker

Let's consider a somewhat simpler case where it would be useful to have a class build our UI for us. Suppose we are going to write a program to keep track of the performance of our investments. We might have stocks, bonds, and mutual funds, and we'd like to display a list of our holdings in each category so we can select one or more of the investments and plot their comparative performance.

Even though we can't predict in advance how many of each kind of investment we might own at any given time, we'd like to have a display that is easy to use for either a large number of funds (such as stocks) or a small number of funds (such as mutual funds). In each case, we want some sort of a multiple-choice display so that we can select one or more funds to plot. If there is a large number of funds, we'll use a multichoice list box, and if there are three or fewer funds, we'll use a set of check boxes. We want our Builder class to generate an interface that depends on the number of items to be displayed and yet have the same methods for returning the results.

Our displays are shown in Figure 13-2. The left display contains a large number of stocks, and the right contains a small number of bonds.

Stocks with the list interface and bonds with the check box interface

Figure 13-2. Stocks with the list interface and bonds with the check box interface

Now let's consider how we can build the interface to carry out this variable display. We'll start with a multiChoice interface that defines the methods we need to implement.

'Interface MultiChoice
'This is the interface for the multi-Select windows
'-----
'get collection of all selected stocks
Public Function getSelected() As Collection
End Function
'-----
'get window containing multichoice controls
Public Function getWindow() As Form
End Function
'-----
'store list of stocks
Public Sub init(stocks As Collection)
End Sub

The getWindow method returns a window with a multiple-choice display. The two displays we're using here, a check box panel or a list box panel, implement this interface.

'checkBox form
Implements MultiChoice

or

'Listbox form
Implements MultiChoice

Then we create a simple Factory class that decides which of these two classes to return.

'Class StockFactory
'gets correct window for number of stocks presented
Public Function getBuilder(stocks As Collection)
 Dim mult As MultiChoice
 If stocks.Count <= 3 Then
   Set mult = New checkForm  'get check box form
 Else
   Set mult = New listForm   'get list box form
 End If
 mult.init stocks            'initialize it
 Set getBuilder = mult
End Function

In the language of Design Patterns, this simple factory class is called the Director, and the actual classes derived from multiChoice are each Builders.

Calling the Builders

Visual Basic 6 doesn't make it easy to create and change forms dynamically during program execution, so we will instead create instances of the various types of multiChoice windows as components of an MDI form. Then we'll instantiate the one needed by the number of equities in a specific category.

Our user interface consists of an MDI form with the list of fund choices on the left and initially nothing on the right. Since windows grow to fill the available space, will fill the right space initially with a blank form, as shown in Figure 13-3.

The fund choice before any fund type is selected

Figure 13-3. The fund choice before any fund type is selected

We first define the interface Equities.

'Interface to Equities class
Public Function getFunds() As Collection
  'returns list of fund names
End Function

Then we'll keep our three lists of investments in three classes called Stocks, Bonds, and Mutuals, each of which implements Equities. We create an instance of each of them in the Funds class and fetch one or the other of them from that class.

'Class funds
Private fundList As New Collection
Private eq As Equities
Private Sub Class_Initialize()
 'Creates a collection of equities
 fundList.Add New stocks
 fundList.Add New Bonds
 fundList.Add New Mutuals
End Sub
'-----
Public Function getFund(i As Integer) As Equities
 'return selected equity from collection
 If i > 0 And i <= fundList.Count Then
   Set getFund = fundList(i)
 End If
End Function

We load them with arbitrary values as part of program initialization.

'Class stocks
Implements Equities
Private stockList As New Collection

Private Sub Class_Initialize()
 'add in arbitrary list of stocks
    stockList.Add "Cisco"
    stockList.Add "Coca Cola"
    stockList.Add "GE"
    stockList.Add "Harley Davidson"
    stockList.Add "IBM"
    stockList.Add "Microsoft"
End Sub
'-----
Private Function Equities_getFunds() As Collection
 'return collection of stocks
    Set Equities_getFunds = stockList
End Function

It is the same for Bonds and Mutuals. In a real system, we'd probably read them in from a file or database. Then, when the user clicks on one of the three investment types in the left list box, we get the correct Equity from the Funds class and use it to create the right display.

Private Sub fundList_Click()
 Dim i As Integer, selStocks As Collection, eq As Equities

 'catch list box click selecting fund type
 i = fundList.ListIndex + 1
 If (i > 0) Then
   'get one type of fund
   Set eq = fnds.getFund(i)      'get equit from funds class
   Set selStocks = eq.getFunds   'get list from equity
   'get a multiChoice form from the factory
   Set mchoice = sfact.getBuilder(selStocks)
   'tell the parent MDI to show it
   mParent.setShowForm mchoice
 End If
End Sub

The List Box Builder

The simpler of the two Builders is the List Box Builder. It returns a form containing a list box showing the list of the investments in that equity type.

'Listbox form
Implements MultiChoice
Private sels As Collection
'-----
Private Sub MultiChoice_init(stocks As Collection)
 Private i As Integer
 For i = 1 To stocks.Count
   List1.AddItem stocks(i)
 Next i
End Sub

The other important method is the getSelected method that returns a collection of Strings of the investments the user selects.

Private Function MultiChoice_getSelected() As Collection
Dim i As Integer
Set sels = New Collection
For i = 0 To List1.ListCount - 1
  If List1.Selected(i) Then
    sels.Add List1.List(i)
  End If
Next i
Set MultiChoice_getSelected = sels
End Function

The Check Box Builder

The Check Box Builder is also quite simple. Here we need to find out how many elements are to be displayed and display them. The number can only be between 0 and 3, so we create them all in advance and display only those we need.

Private Sub MultiChoice_init(stocks As Collection)
 Dim i As Integer
 'set captions for the check boxes we are using
 For i = 1 To stocks.Count
   ckFunds(i - 1).Caption = stocks(i)
 Next i
 'make the rest invisible
 For i = stocks.Count + 1 To 3
   ckFunds(i).Visible = False
 Next i
End Sub

The getSelected method is analogous to the preceding one. It is shown here. We illustrate the final UML class diagram in Figure 13-4.

The Builder class diagram

Figure 13-4. The Builder class diagram

Implements MultiChoice
Private sels As Collection
'-----
Private Function MultiChoice_getSelected() As Collection
 Dim i As Integer
 'create collection of checked stock names
 Set sels = New Collection
 For i = 1 To 3
   If ckFunds(i - 1).Value = 1 Then
    sels.Add ckFunds(i - 1).Caption
  End If
 Next i
 'return collection to caller
 Set MultiChoice_getSelected = sels
End Function

Writing a Builder in VB.NET

VB7 gives us considerably more flexibility in designing Builder classes, since we have direct access to the methods that allow us to construct a window from basic components. For this example, we'll let each builder construct a Panel containing whatever components it needs. We can then add that Panel to the form and position it. When the display changes, you remove the old Panel and add a new one. VB6 does not have a Panel class, but in VB7, a Panel is just an unbordered container that can hold any number of Windows components. As we did previously, the two implementations of the Panel will satisfy the MultiChoice interface.

Public Interface MultiChoice
    'an interface to any group of components
    'that can return zero or more selected items
    'the names are returned in an Arraylist
    Function getSelected() As ArrayList
    Sub clear()             'clear all selected
    Function getWindow() As Panel
End Interface

We will create a base abstract class called Equities and derive the stocks, bonds, and mutual funds from it.

Public MustInherit Class Equities
    Protected ar As Arraylist
    '-----
     Public Overrides Function toString() As String
        'must override this one
    End Function
    '-----
    Public Function getNames() As ArrayList
        Return ar
    End Function
    '-----
    Public Function count() As Integer
        Return ar.count
    End Function
    '-----
End Class

Note the toString method. We'll use this to display each kind of equity in the list box. Now our Stocks class will just contain the code to load the ArrayList with the stock names.

Public Class Stocks
    Inherits Equities
    '-----
    Public Sub New()
        MyBase.New()
        ar = New ArrayList()
        ar.Add("Cisco")
        ar.Add("Coca Cola")
        ar.Add("GE")
        ar.Add("Harley Davidson")
        ar.Add("IBM")
        ar.Add("Microsoft")
    End Sub
    '-----
    Public Overrides Function toString() As String
        Return "Stocks"
    End Function
End Class

All the remaining code (getNames and count) is implemented in the base Equities class. The Bonds and Mutuals classes are equally simple.

The Stock Factory

We need a little class to decide whether we want to return a check box panel or a list box panel. We'll call this class the StockFactory class. However, we will never need more than one instance of this class, so we'll create the class so its one method is Shared.

Public Class StockFactory
    'This class has only one shared method
    Public Shared Function getBuilder(ByVal _
            stocks As Equities) _
             As MultiChoice

        Dim mult As MultiChoice

        If stocks.Count <= 3 Then
            'get check boxes
            mult = New checkChoice(stocks)
        Else
            'get a list box
            mult = New listChoice(stocks)
        End If
        Return mult
    End Function
End Class

The CheckChoice Class

Our Check Box Builder constructs a panel containing 0 to 3 check boxes and returns that panel to the calling program.

Public Class CheckChoice
    Implements MultiChoice

    Private stocks As ArrayList
    Private pnl As Panel
    Private boxes As ArrayList
    '-----
    'create a Panel containing
    '0 to 3 check boxes
    Public Sub New(ByVal stks As Equities)
        MyBase.New()
        stocks = stks.getNames
        pnl = New Panel()
        boxes = New Arraylist() 'in an ArrayList
        Dim i As Integer
        For i = 0 To stocks.count - 1
            Dim Ck As New Checkbox()
            Ck.Location = _
               New System.Drawing.Point(8, 16 + i * 32)
            Ck.Text = stocks(i).toString
            Ck.Size = New Size(112, 24)
            Ck.TabIndex = 0
            Ck.TextAlign = _
               ContentAlignment.MiddleLeft
            boxes.add(ck)              'internal array
            pnl.Controls.add(ck)       'add into panel
        Next i

    End Sub

End Class

The methods for returning the window and the list of selected names are shown here. Note that we use the Ctype function to convert between the Object type returned by an ArrayList and the Checkbox type the method actually requires.

    'clear all selected check boxes
    Public Sub clear() Implements MultiChoice.clear
        Dim i As Integer
        Dim ck As Checkbox
        For i = 0 To boxes.count - 1
            ck = CType(boxes(i), Checkbox)
            ck.Checked = False
        Next i
    End Sub
'--------
    'gets list of selected names
    Public Function getSelected() As ArrayList _
             Implements MultiChoice.getSelected
        Dim ar As New ArrayList()
        Dim i As Integer
        Dim ck As Checkbox

        For i = 0 To Boxes.count - 1
            ck = CType(boxes(i), Checkbox)
            If ck.Checked Then
                ar.add(ck.Text)
            End If
        Next i
        Return ar
    End Function
    '--------
    'gets the Panel containing the check boxes
    Public Function getWindow() As Panel _
            Implements MultiChoice.getWindow
        Return pnl
    End Function

The ListboxChoice Class

This class creates a multiselect list box, inserts it into a Panel, and loads the names into the list.

Public Class ListChoice
    Implements MultiChoice

    Private stocks As ArrayList
    Private pnl As Panel
    Private lst As ListBox
    '--------
    'create a panel containing a
    'multiselectable list box
    Public Sub New(ByVal stks As Equities)
        MyBase.New()
        stocks = stks.getNames 'get the names
        pnl = New Panel()
        'create the list box
        lst = New ListBox()
        lst.Location = New Point(16, 0)
        lst.Size = New Size(120, 160)
        lst.SelectionMode = _
              SelectionMode.MultiExtended
        lst.TabIndex = 0
        'add it into the panel
        pnl.Controls.Add(lst)
        'add the names into the list
        Dim i As Integer
        For i = 0 To stks.count - 1
            lst.items.add(stocks(i))
        Next i
    End Sub

Since this is a multiselect list box, we can get all the selected items in a single SelectedIndices collection. This method, however, only works for a multiselect list box. It returns a –1 for a single-select list box. We use it to load the array list of selected names as follows.

Public Function getSelected() As ArrayList _
             Implements MultiChoice.getSelected
        Dim i As Integer
        Dim item As String
        Dim arl As New ArrayList()

        'get items and put in ArrayList
        For i = 0 To lst.SelectedIndices.count - 1
            item = lst.Items( _
                lst.SelectedIndices(i)).toString
            arl.add(item)
        Next i
        Return arl    'return the ArrayList
    End Function
    '-----
    'clear all selected items
    Public Sub clear() Implements MultiChoice.clear
        lst.Items.clear()
    End Sub
    '-----
    'return the constructed panel
    Public Function getWindow() As Panel _
            Implements MultiChoice.getWindow
        Return pnl
    End Function

Using the Items Collection in the ListBox Control

You are not limited to populating a list box with strings in VB7. When you add data to the Items collection, it can be any kind of object that has a toString method. This takes the place of the much more limited Itemdata property of the listbox in VB6.

Since we created our three Equities classes to have a toString method, we can add them directly to the list box in our main program's constructor.

Public Class wBuilder
    Inherits System.Windows.Forms.Form
    Private Pnl As Panel
    Private mchoice As MultiChoice
    Private eq As Equities
    '-----
    Private Sub init()
        lsEqTypes.Items.Add(New Stocks())
        lsEqTypes.Items.Add(New Bonds())
        lsEqTypes.Items.Add(New Mutuals())
    End Sub

Whenever we click on a line of the list box, the click method obtains that instance of an Equities class and passes it to the MultiChoice factory, which in turn produces a Panel containing the items in that class. It then removes the old panel and adds the new one.

Private Sub lsEqTypes_SelectedIndexChanged( _
    ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles lsEqTypes.SelectedIndexChanged

    Dim i As Integer
    i = lsEqTypes.SelectedIndex

    'get the Equity from the list box
    eq = CType(lsEqTypes.Items(i), Equities)

    'get the right Builder
    mchoice = StockFactory.getBuilder(eq)

    'remove the old panel
    Me.Controls.Remove(Pnl)

    'get the new one and add it to the window
    Pnl = mchoice.getWindow
    setPanel()
End Sub

The Final Choice

Now that we have created all the needed classes, we can run the program. It starts with a blank panel on the right side, so there will always be some panel there to remove. Then each time we click on one of the names of the Equities, that panel is removed and a new one is added in its place. We see the three cases in Figure 13-5.

The VB7 WealthBuilder program

Figure 13-5. The VB7 WealthBuilder program

You can see the relationships between the classes in the UML diagram in Figure 13-6.

The inheritance relationships in the Builder pattern

Figure 13-6. The inheritance relationships in the Builder pattern

Consequences of the Builder Pattern

  1. A Builder lets you vary the internal representation of the product it builds. It also hides the details of how the product is assembled.

  2. Each specific Builder is independent of the others and of the rest of the program. This improves modularity and makes the addition of other Builders relatively simple.

  3. Because each Builder constructs the final product step by step, depending on the data, you have more control over each final product that a Builder constructs.

A Builder pattern is somewhat like an Abstract Factory pattern in that both return classes made up of a number of methods and objects. The main difference is that while the Abstract Factory returns a family of related classes, the Builder constructs a complex object step by step, depending on the data presented to it.

Thought Questions

Thought Questions
  1. Some word-processing and graphics programs construct menus dynamically based on the context of the data being displayed. How could you use a Builder effectively here?

  2. Not all Builders must construct visual objects. What might you construct with a Builder in the personal finance industry? Suppose you were scoring a track meet, made up of five or six different events. How can you use a Builder there?

Programs on the CD-ROM

BuildersSimpleBuilder VB6 basic equities Builder
BuildersVBNetBuilder VB7 equities Builder
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset