본문 바로가기
James Cooper - Python Programming with D

The Prototype Pattern

by 자동매매 2023. 3. 23.

The Builder Pattern

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 depending on the data we need to display. A typical example might be your email address book. You probably have both 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, email address, and phone number.

On the other hand, if you display a group address page, you want to see the name of the group, its purpose, and a list of members and their email addresses. You click a person entry and get one display, and you click a group entry and get the other display. Let’s assume that all email addresses are kept in an object called an Address and that people and groups are derived from this base class (see Figure 9-1).

Figure 9-1 Simple address book that shows individuals or groups

Depending on which type of Address object we click, we’d want 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 widgets, in various ways, depending on the data. Furthermore, since Python is one of the few languages where you can cleanly separate the data from the display methods into simple objects, Python is the ideal language to implement Builder patterns.

 

An Investment Tracker

Let’s consider a somewhat simpler case in which you want to have a class build your UI. Suppose you are going to write a program to keep track of the performance of your investments. You might have stocks, bonds, and mutual funds. Let’s say you want to display a list of your holdings in each category so that you can select one or more investments and plot their comparative performance.

Even though you can’t predict in advance how many of each kind of investment you might own at any given time, you 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, you want some sort of a multiple-choice display so that you can select one or more funds to plot. For a large number of funds, you can use a multichoice list box. For three or fewer funds, you can use a set of check boxes. Here you want your Builder class to generate an interface that depends on the number of items to be displayed, yet have the same methods for returning the results.

The next two figures show the displays. Figure 9-2 shows the first display, for a large number of stocks. Figure 9-3 shows the second display, for a small number of bonds.

Figure 9-2 Wealth builder with a Stocks display

Figure 9-3 Wealth builder with a Bonds display

Then, by clicking the Show button, you can display which securities were selected, regardless of which type of display you have chosen (see Figure 9- 4).

Figure 9-4 Display of selected securities

Now let’s consider how to build the interface to carry out this variable display. We’ll start with a MultiChoice abstract class that defines the methods we need to implement:

class MultiChoice:
    def __init__(self, frame, choiceList):
        self.choices = choiceList    #save list
        self.frame = frame

   #to be implemented in derived classes
    def makeUI(self): pass  #fill a Frame of components
    def getSelected(self): pass  #get list of items

    #clears out the components of the frame
    def clearAll(self):
        for widget in self.frame.winfo_children():
            widget.destroy()

 

Note the clearAll method in the base class. It simply deletes all components from the Frame and works whether the frame contains a Listbox or a set of CheckBoxes. We use the same CheckBox class we developed in Chapter 2, “Visual Programming in Python,” because it keeps the IntVal indicating whether the box is checked.

The makeUI method fills a Frame with a multiple-choice display. The two displays we’re using here are a checkbox panel and a list box panel, both derived from this abstract class:

Click here to view code image

class ListboxChoice(MultiChoice):

or

class CheckboxChoice(MultiChoice):

Then you create a simple Factory class that decides which of these two classes to return:

class ChoiceFactory:
   """This class returns a Panel containing
   a set of choices displayed by one of
   several UI methods. """
   def getChoiceUI(self, choices, frame):
        if len(choices) <=3:
        #return a panel of checkboxes
            return CheckboxChoice(frame, choices)
        else:
        #return a multi-select list box panel
            return ListboxChoice(frame, choices)

 

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

 

Calling the Builders

We’re going to need one or more builders, so we might have called the main class Architect or Contractor. However, we’re dealing with lists of investments in this example, so we’ll just call it WealthBuilder. In this main class, we create the user interface, consisting of a Frame with the center divided into a 1 × 2 grid layout (see Figures 9-2 and 9-3). The left part

contains our list of investment types and the right part is an empty panel that we’ll fill depending on which kind of investments are selected. The second grid row contains the Show button, which has a columnspan of 2.

class BuildUI():
    def __init__(self, root):
        self.root = root
        self.root.geometry("250x200")
        self.root.title("Wealth Builder")
        self.seclist=[] #start with empty list

 

In this simple program, we keep our three lists of investments in three instances of the Securities class, which has a name and a list of security names of that type. We load them with arbitrary values as part of program initialization:

    def build(self):
        # create securities list
        self.stocks= Securities("Stocks", ["Cisco", "Coca Cola", "General Electric", "Harley-Davidson", "IBM"])
        self.seclist.append(self.stocks)
        self.bonds = Securities("Bonds", ["CT State GO 2024", "New York GO 2026", "GE Corp Bonds"] )
        self.seclist.append(self.bonds)
        self.mutuals = Securities("Mutuals", ["Fidelity Magellan", "T Rowe Price", "Vanguard Primecap", "Lindner"])
        self.seclist.append(self.mutuals)

 

In a real system, we’d probably read them in from a file or database. Then when the user clicks one of the three investment types in the left Listbox, we pass the equivalent Securities class to your Factory, which returns one

of the Builders:

    def lbselect(self, evt):
        index = self.leftList.curselection()  # returns a tuple
        i = int(index[0])  # this is the actual index
        securities = self.seclist[i]
        cf = ChoiceFactory()
        self.cui = cf.getChoiceUI(securities.getList(), self.rframe)
        self.cui.makeUI()         #creates right hand panel

 

We do save the MultiChoice panel that the factory creates in the cui variable so that we can pass it to the Plot dialog.

 

The List Box Builder

The simpler of the two builders is the Listbox builder. It returns a panel containing a Listbox that shows the list of investments.

class ListboxChoice( MultiChoice):

    def  __init__(self, frame, choices):
        super().__init__(frame, choices)

    # creates and loads the listbox into the frame
    def makeUI(self):
       self.clearAll()
      #create a frame containing a list box
       self.list = Listbox(self.frame, selectmode=MULTIPLE)	#list box
       self.list.pack()

    #add investments into list box
       for st in self.choices:
           self.list.insert(END, st)

 

The other important method in this class is the getSelected method. It returns a String array of the investments the user selects:

    def getSelected(self):
        sel = self.list.curselection()
        selist=[]
        for i in sel:
            st = self.list.get(i)
            selist.append(st)
        return selist

 

The Checkbox Builder

The Checkbox builder is even simpler. Here we need to find out how many elements are to be displayed and create a horizontal grid of that many divisions. Then we insert a check box in each grid line:

class CheckboxChoice(MultiChoice):
    def __init__(self, panel, choices):
        super().__init__(panel, choices)

    #creates the checkbox UI
    def makeUI(self):
        self.boxes = []  # list of check boxes stored here
        self.clearAll()
        r = 0
        for name in self.choices:
            var = IntVar()  # create an IntVar
            cb = Checkbox(self.frame, name, var)  # create checkbox
            self.boxes.append(cb)  # add it to list
            cb.grid(column=0, row=r, sticky=W)  # grid layout
            r += 1

     # returns list of selected check boxes
    def getSelected(self):
        items=[]
        for b in self.boxes:
            if b.getVar() > 0:
                items.append(b.getText())
        return items

 

Displaying the Selected Securities

When you click the Show button, the button asks the builder for the current UI and obtains a list of the selected securities, as shown earlier with the lbselect method. Note that this calls the securities.getList() method,

which returns the list of those selected, regardless of which interface is

being displayed, because both the ListBoxChoice and the CheckBoxChoice classes have that getList method.

 

Consequences of the Builder Pattern

The consequences of the Builder pattern include the following:

  1. A Builder enables you to 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 also of the rest of the program. This improves modularity and makes adding 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 it constructs.

A Builder pattern is somewhat like an Abstract Factory pattern, in that both return classes are made up of a number of methods and objects. The main difference is that whereas 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 can you use a Builder effectively here?
  2. Not all Builders must construct visual objects. What might you use a Builder to construct in the personal finance industry? Suppose you were scoring a track meet made up of five to six different events? Can you use a Builder there?

Sample Code on GitHub

  • BuilderChoices.py: Creates the WealthBuilder display

'James Cooper - Python Programming with D' 카테고리의 다른 글

Summary of Creational Patterns  (0) 2023.03.23
The Prototype Pattern  (0) 2023.03.23
The Singleton Pattern  (0) 2023.03.23
The Abstract Factory Pattern  (0) 2023.03.23
The Factory Method Pattern  (0) 2023.03.23

댓글