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 rather a whole different user interface, depending on the data we need to display. A typical example might be your e-mail address book. You probably have both people and groups of people in your address book, and you expect the display for the address book to change so that the People screen has places for first and last names, company name, e-mail address, and telephone 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 its members and their e-mail addresses. You click on a person's name and get one display and click on a group's name and get the other display. Let's assume that all e-mail addresses are kept in an Address object and that People and Group classes are derived from this base class, as shown in Figure 7.1.
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 little more than just a Factory pattern because the objects returned are not simple descendents of a base display object, but rather totally different user interfaces made up of different combinations of display objects. The Builder pattern assembles a number of objects, such as display widgets, in various ways depending on the data. Furthermore, since Java is one of the few languages with which you can cleanly separate the data from the display methods into simple objects, Java is the ideal language to implement the Builder pattern.
Let's consider a somewhat simpler case in which it would be useful to have a class build our GUI for us. Suppose that we want to write a program to keep track of the performance of our investments, for example, stocks, bonds, and mutual funds. We want to display a list of our holdings in each category so that we can select one or more of the investments and plot their comparative performances.
Even though we can't predict in advance how many of each kind of investment we might own at any given time, we want 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 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; 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 7.2. The first display, Figure 7.2(a), contains a large number of stocks, and the second, Figure 7.2(b), a small number of bonds.
Figure 7.2. (a) Display of stocks showing the list interface and (b) display of bonds showing 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 abstract class that defines the methods that we need to implement.
public abstract class multiChoice { //the abstract base class //from which the list box and check box choice panels //are derived private Vector choices; //array of labels public multiChoice(Vector choiceList) { choices = choiceList; //save list } //to be implemented in derived classes abstract public Panel getUI(); //return Panel of components abstract public String[] getSelected(); //get list abstract public void clearAll(); //clear all }
The getUI method returns a Panel container that has a multiple choice display. The two displays we're using here—a check box panel and a list box panel—are derived from this abstract class.
class listBoxChoice extends multiChoice
or
class checkBoxChoice extends multiChoice
Then we create a simple Factory class that decides which of the following two classes to return:
public class choiceFactory { multiChoice ui; //class returns a Panel containing //a set of choices displayed by one of //several UI methods public multiChoice getChoiceUI(Vector choices) { if (choices.size() <=3) //return a panel of check boxes ui = new checkBoxChoice(choices); else //return a multiselect list box panel ui = new listBoxChoice(choices); return ui; } }
In the language of Design Patterns, this simple factory class is called the Director, and each actual class derived from multiChoice is a Builder.
Since we're going to need one or more builders, we might call our main class Architect or Contractor. However, here we're dealing with lists of investments, so we'll just call it wealthBuilder. In this main class, we create the user interface, consisting of a BorderLayout with the center divided into a 1-×-2 GridLayout.
The left part of the grid contains our list of investment types and the right an empty panel that we'll fill depending on the kinds of investments selected.
public class wealthBuilder extends JxFrame implements ListSelectListener, ActionListener { private JawtList stockList; //list of funds private JButton Plot; //plot command button private JPanel choicePanel; //right panel private multiChoice mchoice; //ui for right panel private Vector Bonds, Stocks, Mutuals; //3 lists private choiceFactory cfact; //the factory public wealthBuilder() { super("Wealth Builder"); //frame title bar setGUI(); //set up display buildStockLists(); //create stock lists cfact = new choiceFactory( //create builder factory } //-------------- private void setGUI() { JPanel jp = new JPanel(); getContentPane().add (jp); jp.setLayout(new BorderLayout()); JPanel p = new JPanel(); jp.add("Center", p); //center contains left and right panels p.setLayout(new GridLayout(1,2)); stockList= new JawtList(10) //left list of stocks stockList.addListSelectionListener(this); p.add(stockList); stockList.add("Stocks"); stockList.add("Bonds"); stockList.add("Mutual Funds"); stockList.addListSelectionListener(this); JPanel p1 = new JPanel(); p1.setBackground(Color.lightGray); jp.add("South", p1); Plot = new JButton("Plot"); Plot.setEnabled(false); //disabled until picked Plot.addActionListener(this); p1.add(Plot); //right is empty at first choicePanel = new JPanel(); choicePanel.setBackground(Color.lightGray); p.add(choicePanel); setBounds(100, 100, 300, 200); setVisible(true); }
In this simple program, we keep our three lists of investments in three Vectors—Stocks, Bonds, and Mutuals. We load them with arbitrary values as part of program initializing.
Vector Mutuals = new Vector(); Mutuals.addElement("Fidelity Magellan"); Mutuals.addElement("T Rowe Price"); Mutuals.addElement("Vanguard PrimeCap"); Mutuals.addElement("Lindner Fund");
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 pass the equivalent Vector to our Factory, which returns one of the Builders:
private void stockList_Click() { Vector v = null; int index = stockList.getSelectedIndex(); choicePanel.removeAll(); //remove previous gui panel //switches between three different Vectors //and passes the selected one to the Builder pattern switch (index) { case 0: v = Stocks; break; case 1: v = Bonds; break; case 2: v = Mutuals; } mchoice = cfact.getChoiceUI(v); //get one of the guis choicePanel.add(mchoice.getUI()); //insert on right choicePanel.validate(); //re-layout and display choicePanel.repaint (); Plot.setEnabled(true); //allow plots }
We show this switch code for simplicity. We could just as easily have attached adifferent ActionListener to each of the three buttons. We do save the multiChoice panel that the factory creates in the mchoice variable so that we can pass it to the Plot dialog.
The simpler of the two builders is the list box builder. The getUI method returns a panel containing a list box showing the list of investments.
public class listBoxChoice extends multiChoice { JawtList list; //-------------- public listBoxChoice(Vector choices) { super(choices); } //-------------- public JPanel getUI() { //create a panel containing a list box JPanel p = new JPanel(); list = new JawtList(choices.size()); list.setMultipleMode(true); p.add(list); for (int i = 0; i < choices.size(); i++) list.add((String)choices.elementAt(i)); return p; }
The other important method is the getSelected method, which returns a String array of the investments that the user selects.
public String[] getSelected() { String[] slist = list.getSelectedItems (); return(slist); }
The Check box Builder is even simpler. Here we need to find out how many elements are to be displayed and then create a horizontal grid of that many divisions. Then we insert a check box in each grid line.
public class checkBoxChoice extends multiChoice {
//This derived class creates
//vertical grid of check boxes
int count; //number of check boxes
JPanel p; //contained here
//--------------
public checkBoxChoice (Vector choices) {
super(choices);
count = 0;
p = new JPanel();
}
//--------------
public JPanel getUI() {
String s;
//create a grid layout 1 column x n rows
p.setLayout(new GridLayout (choices.size(), 1));
//and add labeled check boxes to it
for (int i = 0; i< choices.size(); i++) {
s =(String)choices.elementAt(i);
p.add(new JCheckBox(s));
count++;
}
return p;
}
This getSelected method is analogous to the method shown previously and is included in the example code on the CD-ROM. We illustrate the final UML class diagram in Figure 7.3.
Using a Builder pattern has the following consequences:
A Builder pattern lets you vary the internal representation of the product that it builds. It also hides the details of how the product is assembled.
Each specific Builder is independent of any others and of the rest of the program. This improves modularity and makes the addition of other Builders relatively simple.
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.
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?
Not all Builders must construct visual objects. For the personal finance industry, what could you construct using a Builder? Suppose that you are scoring a track meet made up of five to six different events. How can you use a Builder in such a situation?