One type of pattern that we see again and again in OO programs is the Simple Factory pattern. A Simple Factory pattern is one that returns an instance of one of several possible classes, depending on the data provided to it. Usually all of the classes it returns have a common parent class and common methods, but each of them performs a task differently and is optimized for different kinds of data. This Simple Factory is not, in fact, one of the 23 GoF patterns, but it serves here as an introduction to the somewhat more subtle Factory Method GoF pattern we'll discuss shortly.
To understand the Simple Factory pattern, let's look at the diagram in Figure 9-1.
In Figure 9-1, X is a base class, and classes XY and XZ are derived from it. The XFactory class decides which of these subclasses to return, depending on the arguments you give it. On the right, we define a getClass method to be one that passes in some value abc and that returns some instance of the class x. Which one it returns doesn't matter to the programmer, since they all have the same methods but different implementations. How it decides which one to return is entirely up to the factory. It could be some very complex function, but it is often quite simple.
Let's consider a simple VB6 case where we could use a Factory class. Suppose we have an entry form and we want to allow the user to enter his name either as “firstname lastname” or as “lastname, firstname.” We'll make the further simplifying assumption that we will always be able to decide the name order by whether there is a comma between the last and first name.
This is a pretty simple sort of decision to make, and you could make it with a simple if statement in a single class, but let's use it here to illustrate how a factory works and what it can produce. We'll start by defining a simple interface that takes the name string in and allows you to fetch the names back.
Public Sub init(ByVal s As String) End Sub '----- Public Function getFrName() As String End Function '----- Public Function getLname() As String End Function
Now we can write two very simple classes that implement that interface and split the name into two parts in the constructor. In the FNamer class, we make the simplifying assumption that everything before the last space is part of the first name.
'Class FNamer Implements Namer Private nm As String, lname As String, frname As String '----- Private Function Namer_getFrname() As String Namer_getFrname = frname End Function '----- Private Function Namer_getLname() As String Namer_getLname = lname End Function '----- Private Sub Namer_init(ByVal s As String) Dim i As Integer nm = s i = InStr(nm, " ") 'look for space If i > 0 Then frname = Left$(nm, i - 1) 'separate names lname = Trim$(Right$(nm, Len(nm) - i)) Else lname = nm 'or put all in last name frname = "" End If End Sub
And in the LNamer class, we assume that a comma delimits the last name. In both classes, we also provide error recovery in case the space or comma does not exist.
'Class LNamer Implements Namer Private nm As String, lname As String, frname As String '----- Private Function Namer_getFrname() As String Namer_getFrname = frname End Function '----- Private Function Namer_getLname() As String Namer_getLname = lname End Function '----- Private Sub Namer_init(ByVal s As String) Dim i As Integer nm = s 'save whole name i = InStr(nm, ",") 'if comma, last is to left If i > 0 Then lname = Left$(nm, i - 1) frname = Trim$(Right$(nm, Len(nm) - i)) Else lname = nm 'or put all in last name frname = "" End If End Sub
Now our Simple Factory class is easy to write and is part of the user interface. We just test for the existence of a comma and then return an instance of one class or the other.
Private nmer As Namer 'will be one kind or the other '----- Private Sub getName_Click() Dim st As String, i As Integer st = txNames.Text 'get the name from the entry field i = InStr(st, ",") 'look for a comma If i > 0 Then Set nmer = New lNamer 'create last name class Else Set nmer = New Frnamer 'or fist name class End If nmer.init st 'put results in display fields txFrName.Text = nmer.getFrName txlName.Text = nmer.getLname End Sub
Let's see how we put this together. The complete class diagram is shown in Figure 9-2.
We have constructed a simple user interface that allows you to enter the names in either order and see the two names separately displayed. You can see this program in Figure 9-3.
You type in a name and then click on the Compute button, and the divided name appears in the text fields below. The crux of this program is the compute method that fetches the text, obtains an instance of a Namer class, and displays the results.
And that's the fundamental principle of the Simple Factory pattern. You create an abstraction that decides which of several possible classes to return, and it returns one. Then you call the methods of that class instance without ever knowing which subclass you are actually using. This approach keeps the issues of data dependence separated from the classes' useful methods.
In VB7, we can get a fair amount of mileage out of using inheritance here. We can define a base class called NameClass that holds the first and last name in protected variables and define the two accessor functions to get the first and last name out of the variables.
Public Class NameClass Protected Lname, Frname As String Public Function getFirst() As String Return Frname End Function Public Function getLast() As String Return Lname End Function End Class
Then we can derive the FirstFirst and LastFirst classes from this class and make use of the underlying get methods. The complete FirstFirst class is just this.
Public Class FirstFirst Inherits NameClass Public Sub New(ByVal nm As String) Dim i As Integer i = nm.indexOf(" ") If i > 0 Then Frname = nm.substring(0, i).trim() Lname = nm.substring(i + 1).trim() Else Frname = "" LName = nm End If End Sub End Class
And the LastFirst class is entirely analogous. The factory class is quite similar but makes use of the constructors.
Public Class NameFactory Public Function getNamer( _ ByVal nm As String) As NameClass Dim i As Integer i = nm.indexOf(",") If i > 0 Then Return New LastFirst(nm) Else Return New FirstFirst(nm) End If End Function End Class
You can see the difference in how these classes relate in Figure 9-4.
Most people who use Factory patterns tend to think of them as tools for simplifying tangled programming classes. But it is perfectly possible to use them in programs that simply perform mathematical computations. For example, in the Fast Fourier Transform (FFT), you evaluate the following four equations repeatedly for a large number of point pairs over many passes through the array you are transforming. Because of the way the graphs of these computations are drawn, the following four equations constitute one instance of the FFT “butterfly.” These are shown as Equations 1–4.
However, there are a number of times during each pass through the data where the angle y is zero. In this case, your complex math evaluation reduces to Equations (5–8).
Then we can make a simple factory class that decides which class instance to return. Since we are making Butterflies, we'll call our Factory a Cocoon.
'Class Cocoon 'get back right kind of Butterfly Public Function getButterfly(y As Single) As Butterfly If y = 0 Then Set getButterfly = New addButterfly Else Set getButterfly = New trigButterfly End If End Function
In this example, we create a new instance each time. Since there are only two kinds, we might create them both in advance and return them as needed.
'Class Cocoon1 Private addB As Butterfly, trigB As Butterfly '----- 'create instances in advance Private Sub Class_Initialize() Set addB = New addButterfly Set trigB = New trigButterfly End Sub '----- 'get back right kind of Butterfly Public Function getButterfly(y As Single) As Butterfly If y = 0 Then Set getButterfly = addB Else Set getButterfly = trigB End If End Function
Consider a personal checkbook management program like Quicken. It manages several bank accounts and investments and can handle your bill paying. Where could you use a Factory pattern in designing a program like that?
Suppose you are writing a program to assist homeowners in designing additions to their houses. What objects might a Factory be used to produce?