Chapter 12. The Builder Pattern

In this chapter we’ll examine 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 completely 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 12-1.

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

image

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 are 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 12-2. The top display contains a large number of stocks, and the bottom contains a small number of bonds. 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.

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

image


public interface MultiChoice
{
      ArrayList getSelected();
      void clear();
      Panel getWindow();
}

The getWindow method returns a Panel containing a multiple-choice display. The two display panels we’re using here—a check box panel or a list box panel—implement this interface.


public class CheckChoice:MultiChoice {

or


public class ListChoice:MultiChoice {

C# gives us considerable 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. In C#, a Panel is just a unbordered container that can hold any number of Windows components. The two implementations of the Panel must satisfy the multiChoice interface.

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


public abstract class Equities  {
      protected ArrayList array;
      public abstract string ToString();
      //----------
      public ArrayList getNames() {
             return array;
      }
      //----------
      public int count() {
             return array.Count ;
      }
      }

Note the abstract 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:Equities    {
      public Stocks() {
             array = new ArrayList();
             array.Add ("Cisco");
             array.Add ("Coca Cola");
             array.Add ("GE");
             array.Add ("Harley Davidson");
             array.Add ("IBM");
             array.Add ("Microsoft");
      }
      public override string ToString() {
             return "Stocks";
      }
}

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

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 static.


public class StockFactory {
      public static MultiChoice getBuilder(Equities stocks) {
       if (stocks.count ()<=3) {
             return new CheckChoice (stocks);
       }
       else {
             return new ListChoice(stocks);
       }
      }
}

We never need more than one instance of this class, so we make the getBuilder method static. Then we can call it directly without creating a class instance. In the language of Design Patterns, this simple factory class is called the Director, and the actual classes derived from multiChoice are each Builders.

The CheckChoice Class

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


//returns a panel of 0 to 3 check boxes
public class CheckChoice:MultiChoice     {
      private ArrayList stocks;
      private Panel panel;
      private ArrayList boxes;
//------
      public CheckChoice(Equities stks)              {
      stocks = stks.getNames ();
      panel = new Panel ();
      boxes = new ArrayList ();
      //add the check boxes to the panel
      for (int i=0; i< stocks.Count; i++) {
             CheckBox ck = new CheckBox ();
             //position them
             ck.Location = new Point (8, 16 + i * 32);
             string stk = (string)stocks[i];
             ck.Text =stk;
             ck.Size = new Size (112, 24);
             ck.TabIndex =0;
             ck.TextAlign = ContentAlignment.MiddleLeft ;
             boxes.Add (ck);
             panel.Controls.Add (ck);
      }
}
}

The methods for returning the window and the list of selected names are shown here. Note that we cast the object type returned by an ArrayList to the CheckBox type the method actually requires.


//------
//uncheck all check boxes
public  void clear() {
      for(int i=0; i< boxes.Count; i++) {
        CheckBox ck = (CheckBox)boxes[i];
             ck.Checked =false;
      }
}
//------
//return list of checked items
public ArrayList getSelected() {
      ArrayList sels = new ArrayList ();
      for(int i=0; i< boxes.Count ; i++) {
             CheckBox ck = (CheckBox)boxes[i];
             if (ck.Checked ) {
                    sels.Add (ck.Text );
             }
      }
      return sels;
}
//------
//return panel of check boxes
public Panel getWindow() {
      return panel;
}

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:MultiChoice {
      private ArrayList stocks;
      private Panel panel;
      private ListBox list;
//------
//constructor creates and loads the list box
      public ListChoice(Equities stks) {
             stocks = stks.getNames ();
             panel = new Panel ();
             list = new ListBox ();
             list.Location = new Point (16, 0);
             list.Size = new Size (120, 160);
             list.SelectionMode =SelectionMode.MultiExtended ;
             list.TabIndex =0;
             panel.Controls.Add (list);
             for(int i=0; i< stocks.Count ; i++) {
                    list.Items.Add (stocks[i]);
             }
      }

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.


      //returns the Panel
      //------
      public  Panel getWindow() {
             return panel;
      }
      //returns an array of selected elements
      //------
      public ArrayList getSelected() {
             ArrayList sels = new ArrayList ();
             ListBox.SelectedObjectCollection
                    coll = list.SelectedItems  ;
             for(int i=0; i< coll.Count; i++) {
                    string item = (string)coll[i];
                          sels.Add (item );
             }
             return sels;
      }
      //------
      //clear selected elements
      public void clear() {
             list.Items.Clear();
}

Using the Items Collection in the ListBox Control

You are not limited to populating a list box with strings in C#. When you add data to the Items collection, it can be any kind of object that has a ToString method.

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


public class WealthBuilder : Form     {
      private ListBox lsEquities;
      private Container components = null;
      private Button btPlot;
      private Panel pnl;
      private MultiChoice mchoice;
      private void init() {
             lsEquities.Items.Add (new Stocks());
             lsEquities.Items.Add (new Bonds());
             lsEquities.Items.Add (new Mutuals());
      }
      public WealthBuilder()    {
             InitializeComponent();
             init();
      }

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 void lsEquities_SelectedIndexChanged(object sender,
             EventArgs e) {
      int i = lsEquities.SelectedIndex ;
      Equities eq = (Equities)lsEquities.Items[i];
      mchoice= StockFactory.getBuilder (eq);
      this.Controls.Remove (pnl);
      pnl = mchoice.getWindow ();
      setPanel();

}
//------
private void setPanel() {
      pnl.Location = new Point(152, 24);
      pnl.Size = new Size(128, 168);
      pnl.TabIndex = 1;
      Controls.Add(pnl);
}

Plotting the Data

We don’t really implement an actual plot in this example. However, we did provide a getSelected method to return the names of stocks from either MultiSelect implementation. The method returns an ArrayList of selected items. In the Plot click method, we load these names into a message box and display it.


private void btPlot_Click(object sender, EventArgs e) {
//display the selected items in a message box
      if(mchoice != null) {
             ArrayList ar  = mchoice.getSelected ();
             string ans = "";
             for(int i=0; i< ar.Count ; i++) {
                    ans += (string)ar[i] +" ";
             }
             MessageBox.Show (null, ans,
             "Selected equities", MessageBoxButtons.OK  );
      }
}

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 12-3.

Figure 12-3. The WealthBuilder program, showing the list of equities, the list box, the check boxes, and the plot panel

image

You can see the relationships between the classes in the UML diagram in Figure 12-4.

Figure 12-4. The inheritance relationships in the Builder pattern

image

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

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?

Program on the CD-ROM

image

..................Content has been hidden....................

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