Chapter 10. The Factory Method

We've just seen a couple of examples of the simplest of factories. The factory concept recurs all throughout object-oriented programming, and we find a few examples embedded in VB itself and in other design patterns (such as the Builder pattern). In these cases a single class acts as a traffic cop and decides which subclass of a single hierarchy will be instantiated.

The Factory Method pattern is a clever but subtle extension of this idea, where no single class makes the decision as to which subclass to instantiate. Instead, the superclass defers the decision to each subclass. This pattern does not actually have a decision point where one subclass is directly selected over another class. Instead, programs written to this pattern define an abstract class that creates objects but lets each subclass decide which object to create.

We can draw a pretty simple example from the way that swimmers are seeded into lanes in a swim meet. When swimmers compete in multiple heats in a given event, they are sorted to compete from slowest in the early heats to fastest in the last heat and arranged within a heat with the fastest swimmers in the center lanes. This is referred to as straight seeding.

Now, when swimmers swim in championships, they frequently swim the event twice. During preliminaries everyone competes, and the top 12 or 16 swimmers return to compete against each other at finals. In order to make the preliminaries more equitable, the top heats are circle seeded: The fastest three swimmers are in the center lane in the fastest three heats, the second fastest three swimmers are in the next to center lane in the top three heats, and so forth.

So, how do we build some objects to implement this seeding scheme and illustrate the Factory Method. First, let's design an abstract Events class.

Option Explicit
'Class Events
Private numLanes As Integer
Private swimmers As New Collection  'list of swimmers
'-----
Public Sub init(Filename As String, lanes As Integer)
End Sub
'-----
Public Function getSwimmers() As Collection
 Set getSwimmers = swimmers
End Function
'-----
Public Function isPrelim() As Boolean
End Function
'-----
Public Function isFinal() As Boolean
End Function
'-----
Public Function isTimedFinal() As Boolean
End Function
'-----
Public Function getSeeding() As Seeding
End Function

Grammatically, it would have been better to call this an “Event” class, but “Event” is a reserved word in VB6.

This defines the remaining methods simply without any necessity of filling them in. Then we can implement concrete classes from the Events class, called PrelimEvent and TimedFinalEvent. The only difference between these classes is that one returns one kind of seeding and the other returns a different kind of seeding.

We also define an abstract Seeding class with the following methods.

'Class Seeding
Private numLanes As Integer
Private laneOrder As Collection
Dim asw() As Swimmer
'-----
Public Function getSeeding() As Collection
End Function
'-----
Public Function getHeat() As Integer
End Function
'-----
Public Function getCount() As Integer
End Function
'-----
Public Sub seed()
End Sub
'-----
Public Function getSwimmers() As Collection
End Function
'-----
Public Function getHeats() As Integer
End Function
'-----
Private Function odd(n As Integer) As Boolean
  odd = (n  2) * 2 <> n
End Function
'-----
Public Function calcLaneOrder(lns As Integer) As Collection
  numLanes = lns
 'This function is implemented but not shown here
  ReDim lanes(numLanes) As Integer
End Function
'-----
Public Sub init(swmrs As Collection, lanes As Integer)
End Sub
'-----
Public Function sort(sw As Collection) As Collection
  ReDim asw(sw.count) As Swimmer
  'This function is implemented but not shown here
End Function

Note that we actually included code for the calcLaneOrder and sort functions but omit the code here for simplicity. The derived classes then each create an instance of the base Seeding class to call these functions.

We can then create two concrete seeding subclasses: StraightSeeding and CircleSeeding. The PrelimEvent class will return an instance of CircleSeeding, and the TimedFinalEvent class will return an instance of StraightSeeding. Thus, we see that we have two hierarchies: one of Events and one of Seedings. We see these two hierarchies illustrated in Figures 10-1 and 10-2.

The class relations between Seeding classes

Figure 10-1. The class relations between Seeding classes

The Event classes

Figure 10-2. The Event classes

In the Events hierarchy, you will see that both derived Events classes contain a getSeeding method. One of them returns an instance of StraightSeeding and the other an instance of CircleSeeding. So you see, there is no real factory decision point as we had in our simple example. Instead, the decision as to which Event class to instantiate is the one that determines which Seeding class will be instantiated.

While it looks like there is a one-to-one correspondence between the two class hierarchies, there needn't be. There could be many kinds of Events and only a few kinds of Seeding used.

The Swimmer Class

We haven't said much about the Swimmer class, except that it contains a name, club age, seed time, and place to put the heat and lane after seeding. The Event class reads in the Swimmers from some database (a file in our example) and then passes that Collection to the Seeding class when you call the getSeeding method for that event.

The Events Classes

We have seen the previous abstract base Events class. In actual use, we use it to read in the swimmer data (here from a file) and pass it on to instances of the Swimmer class to parse.

Option Explicit
'Class Events
Private numLanes As Integer
Private swimmers As New Collection  'list of swimmers
'-----
Public Sub init(Filename As String, lanes As Integer)
Dim f As Integer, s As String
Dim sw As Swimmer
Dim fl As New vbFile
'read in the data file in the constructor
f = FreeFile
numLanes = lanes
Set swimmers = New Collection
  'read in swimmers from file
  Filename = App.Path & "" & Filename
  fl.OpenForRead Filename
  s = fl.readLine

  While (Not fl.fEof)
    Set sw = New Swimmer    'create each swimmer
    sw.init s               'and initialize it
    swimmers.Add sw         'add to list
    s = fl.readLine              'read another
  Wend
 Close #f
End Sub
'-----
Public Function getSwimmers() As Collection
 Set getSwimmers = swimmers
End Function
'-----
Public Function isPrelim() As Boolean
End Function
'-----
Public Function isFinal() As Boolean
End Function
'-----
Public Function isTimedFinal() As Boolean
End Function
'-----
Public Function getSeeding() As Seeding
End Function

The base Event class has empty methods for whether the event is a prelim, final, or timed final event. We fill in the event in the derived classes.

Our PrelimEvent class just returns an instance of CircleSeeding.

'Class PrelimEvent
Implements Events
Private numLanes As Integer
Private swimmers As Collection
Private evnts As New Events
Private sd As Seeding
Private Sub Class_Initialize()
 Set evnts = New Events
End Sub
'-----
Private Function Events_getSeeding() As Seeding
 Set sd = New CircleSeeding
 sd.init swimmers, numLanes
 Set Events_getSeeding = sd
End Function
'-----
Private Function Events_getSwimmers() As Collection
 Set Events_getSwimmers = swimmers
End Function
'-----
Private Function Events_isFinal() As Boolean
 Events_isFinal = False
End Function
'-----
Private Function Events_isPrelim() As Boolean
 Events_isPrelim = True
End Function
'-----
Private Function Events_isTimedFinal() As Boolean
 Events_isTimedFinal = False
End Function
'-----
Private Sub Events_init(Filename As String, lanes As Integer)
 evnts.init Filename, lanes
 numLanes = lanes
 Set swimmers = evnts.getSwimmers
End Sub

The TimedFinalEvent returns an instance of StraightSeeding.

'Class PrelimEvent
Implements Events
Private numLanes As Integer
Private swimmers As Collection
Private evnts As New Events
Private sd As Seeding
Private Sub Class_Initialize()
 Set evnts = New Events
End Sub
'-----
Private Function Events_getSeeding() As Seeding
 Set sd = New CircleSeeding
 sd.init swimmers, numLanes
 Set Events_getSeeding = sd
End Function

In both cases our events classes contain an instance of the base Events class, which we use to read in the data files.

Straight Seeding

In actually writing this program, we'll discover that most of the work is done in straight seeding. The changes for circle seeding are pretty minimal. So we instantiate our StraightSeeding class and copy in the Collection of swimmers and the number of lanes.

Private Sub Seeding_seed()
Dim lastHeat As Integer, lastlanes As Integer
Dim heats As Integer, i As Integer, j As Integer
Dim swmr As Swimmer

Set sw = sd.sort(sw)
Set laneOrder = sd.calcLaneOrder(numLanes)
count = sw.count
lastHeat = count Mod numLanes
If (lastHeat < 3) And lastHeat > 0 Then
     lastHeat = 3   'last heat must have 3 or more
End If
count = sw.count
lastlanes = count - lastHeat
numheats = lastlanes / numLanes
If (lastHeat > 0) Then
    numheats = numheats + 1
End If
heats = numheats

'place heat and lane in each swimmer's object
  'details on CDROM
'Add in last partial heat
  'details on CDROM
End Sub

This makes the entire array of seeded Swimmers available when you call the getSwimmers method.

Circle Seeding

The CircleSeeding class is derived from StraightSeeding, so it copies in the same data.

'Circle seeding method
Private Sub Seeding_seed()
Dim i As Integer, j As Integer, k As Integer
'get the lane order]
Set laneOrder = sd.calcLaneOrder(numLanes)
Set sw = sd.sort(sw)    'sort the swimmers
strSd.init sw, numLanes 'create Straight Seeding object
strSd.seed              'seed into straight order
numheats = strSd.getHeats   'get the total number of heats
  If (numheats >= 2) Then
      If (numheats >= 3) Then
          circlesd = 3      'seed either 3
      Else
          circlesd = 2      'or 2
      End If
      i = 1
      'copy seeding info into swimmers data
      'details on CDROM
  End If
End Sub

Since the circle seeding calls straight seeding, it copies the swimmer collection and lanes values. Then our call to strSd.seed does the straight seeding. This simplifies things because we will always need to seed the remaining heats by straight seeding. Then we seed the last two or three heats as just shown, and we are done with that type of seeding as well.

Our Seeding Program

In this example, we took a list of swimmers from the Web who competed in the 500-yard freestyle and the 100-yard freestyle and used them to build our TimedFinalEvent and PrelimEvent classes. You can see the results of these two seedings in Figure 10-3.

Straight seeding of the 500 free and circle seeding of the 100 free

Figure 10-3. Straight seeding of the 500 free and circle seeding of the 100 free

Other Factories

Now one issue that we have skipped over is how the program that reads in the swimmer data decides which kind of event to generate. We finesse this here by simply creating the correct type of event when we read in the data. This code is in the Form_Load event.

Dim ev As Events

Set ev = New PrelimEvent    'create a Prelim/final event
ev.init "100free.txt", 6    'read in the data
Seedings.Add ev.getSeeding  'get the seeding and add to collection
lsEvents.AddItem "100 Free"

Set ev = New TimedFinalEvent 'create a new Timed final event
ev.init "500free.txt", 6     'read in the data
Seedings.Add ev.getSeeding   'get the seeeding
lsEvents.AddItem "500 Free"  'and add to collection

Clearly, this is an instance where an EventFactory may be needed to decide which kind of event to generate. This revisits the simple factory with which we began the discussion.

The Seeding Program in VB7

In VB7, we can make effective use of inheritance to make each of these classes substantially simpler. For example, the Events class is an abstract class where we fill in the methods in the derived TimedFinalEvent and PrelimEvent classes. In VB7, these classes differ in that we put the file reading methods in the base Seeding class and let them be used by the derived classes, whereas in VB6, we had to create an instance of the base Event class inside the TimedFinal and Prelim event classes and call its functions specifically. The basic abstract class for Events is now simply the following.

Public Class Events

    Protected numLanes As Integer
    Protected swmmers As Swimmers
    '-----
    Public Sub New(ByVal Filename As String, _
                         ByVal lanes As Integer)
        MyBase.New()
        Dim s As String
        Dim sw As Swimmer
        Dim fl As vbFile

        fl = New vbFile(filename)   'Open the file
        fl.OpenForRead()

        numLanes = lanes            'Remember lane number
        swmmers = New Swimmers()    'list of kids

        'read in swimmers from file
        s = fl.ReadLine

        While Not fl.feof
            sw = New Swimmer(s)    'create each swimmer
            swmmers.Add(sw)        'add to list
            s = fl.ReadLine        'read another
        End While
        fl.closeFile()
    End Sub
    '-----
    Public Function getSwimmers() As ArrayList
        Return swmmers
    End Function
    '-----
    Public Overridable Function isPrelim() As Boolean
    End Function
    '-----
    Public Overridable Function isFinal() As Boolean
    End Function
    '-----
    Public Overridable Function isTimedFinal() As Boolean
    End Function
    '-----
    Public Overridable Function getSeeding() As Seeding
    End Function
End Class

Then our TimedFinalEvent is derived from that and creates an instance of the StraightSeeding class.

Public Class TimedFinalEvent
    Inherits Events

    Public Sub New(ByVal Filename As String, _
              ByVal lanes As Integer)
        MyBase.New(Filename, lanes)
    End Sub
    '-----
    Public Overrides Function getSeeding() As Seeding
        Dim sd As Seeding
        'create seeding and execute it
        sd = New StraightSeeding(swmmers, numLanes)
        sd.seed()
        Return sd
    End Function
    '-----
    Public Overrides Function isFinal() As Boolean
        Return False
    End Function
    '-----
    Public Overrides Function isPrelim() As Boolean
        Return False
    End Function
    '-----
    Public Overrides Function isTimedFinal() As Boolean
        Return True
    End Function
End Class

The PrelimEvent class is basically the same, except that we create an instance of circle seeding and set the prelim and finals flags differently. Here is the getSeeding method.

Public Overrides Function getSeeding() As Seeding
        Return New CircleSeeding(swmmers, numLanes)
End Function

In a similar fashion, the base Seeding class contains the functions sort and getLaneOrder, and the derived classes for Straight and Circle seeding contain only the changed seed methods. You can see the class inheritance structure of this VB7 version in Figure 10-4.

The VB7 Seeding program

Figure 10-4. The VB7 Seeding program

When to Use a Factory Method

You should consider using a Factory method in the following situations.

  • A class can't anticipate which kind of class of objects it must create.

  • A class uses its subclasses to specify which objects it creates.

  • You want to localize the knowledge of which class gets created.

There are several variations on the factory pattern to recognize.

  1. The base class is abstract and the pattern must return a complete working class.

  2. The base class contains default methods and these methods are called unless the default methods are insufficient.

  3. Parameters are passed to the factory telling it which of several class types to return. In this case the classes may share the same method names but may do something quite different.

Thought Question

Thought Question

Seeding in track is carried out from inside to outside lanes. What classes would you need to develop to carry out tracklike seeding as well?

Programs on the CD-ROM

FactorySeeder VB6 version of seeding program
FactorySeedervbNetSeeder VB7 version of seeding program
..................Content has been hidden....................

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