Chapter 11. The Abstract Factory Pattern

The Abstract Factory pattern is one level of abstraction higher than the factory pattern. You can use this pattern when you want to return one of several related classes of objects, each of which can return several different objects on request. In other words, the Abstract Factory is a factory object that returns one of several groups of classes. You might even decide which class to return from that group using a Simple Factory.

Common thought experiment-style examples might include automobile factories. You would expect a Toyota factory to work exclusively with Toyota parts and a Ford factory to use Ford parts. You can consider each auto factory as an Abstract Factory and the parts the groups of related classes.

A GardenMaker Factory

Let's consider a practical example where you might want to use the abstract factory in your application. Suppose you are writing a program to plan the layout of gardens. These could be gardens consisting of annuals, vegetables, or perennials. However, no matter which kind of garden you are planning, you want to ask the same questions.

  1. What are good border plants?

  2. What are good center plants?

  3. What plants do well in partial shade? (And probably a lot more plant questions that we won't get into here.)

We want a base VB6 Garden class that can answer these questions as class methods.

Public Function getCenter() As Plant
End Function
'-----
Public Function getBorder() As Plant
End Function
'-----
Public Function getShade() As Plant
End Function

Our Plant object just contains and returns the plant name.

'Class Plant
Private plantName As String
'-----
Public Sub init(nm As String)
  plantName = nm        'save the plant name
End Sub
'-----
Public Function getName() As String
  getName = plantName   'return the plant name
End Function

In Design Patterns terms, the Garden interface is the Abstract Factory. It defines the methods of concrete class that can return one of several classes. Here, we return central, border, and shade-loving plants as those three classes. The abstract factory could also return more specific garden information, such as soil pH or recommended moisture content.

In a real system, each type of garden would probably consult an elaborate database of plant information. In our simple example we'll return one kind of each plant. So, for example, for the vegetable garden we simply write the following.

'Class VeggieGarden
Implements Garden
Private pltShade as Plant, pltBorder as Plant
Private pltCenter As Plant
'-----
Private Sub Class_Initialize()
 Set pltShade = New Plant
 pltShade.init "Broccoli"
 Set pltBorder = New Plant
 pltBorder.init "Peas"
 Set pltCenter = New Plant
 pltCenter.init "Corn"
End Sub
'-----
Private Function Garden_getBorder() As Plant
  Set Garden_getBorder = pltBorder
End Function
'-----
Private Function Garden_getCenter() As Plant
  Set Garden_getCenter = pltCenter
End Function
'-----
Private Function Garden_getShade() As Plant
  Set Garden_getShade = pltShade
End Function

In a similar way, we can create Garden classes for PerennialGarden and AnnualGarden. Each of these concrete classes is known as a Concrete Factory, since it implements the methods outlined in the parent abstract class. Now we have a series of Garden objects, each of which returns one of several Plant objects. This is illustrated in the class diagram in Figure 11-1.

The major objects in the Gardener program

Figure 11-1. The major objects in the Gardener program

We can easily construct our Abstract Factory driver program to return one of these Garden objects based on the radio button that a user selects, as shown in the user interface in Figure 11-2.

The user interface of the Gardener program

Figure 11-2. The user interface of the Gardener program

How the User Interface Works

This simple interface consists of two parts: the left side, which selects the garden type, and the right side, which selects the plant category. When you click on one of the garden types, this causes the program to return a type of garden that depends on which button you select. At first, you might think that we would need to perform some sort of test to decide which button was selected and then instantiate the right Concrete Factory class. However, a more elegant solution is to just listen for the radio button click and change the current garden. Then, when a user clicks on one of the plant type buttons, the plant type is returned from the current garden, and the name of that plant is displayed.

Private Sub opAnnual_Click()
  Set gden = New AnnualGarden   'select Annual garden
End Sub
'-----
Private Sub opPeren_Click()
  Set gden = New PerenGarden    'select Perennial garden
End Sub
'-----
Private Sub opVeggie_Click()
  Set gden = New VeggieGarden   'select vegetable garden
End Sub

Then, when we are called upon to draw a plant name on the garden display, we erase the old name by XORing it and then draw a new one in its place by getting the correct Plant from the current Garden.

'-----
Private Sub btCenter_Click()
  Set plt = gden.getCenter  'get the center plant
  drawCenter plt.getName    'and draw it's name
End Sub
'-----
Private Sub drawCenter(st As String)
  pcGarden.PSet (1200, 1000)
  pcGarden.Print oldCenter   'XOR out old name
  pcGarden.PSet (1200, 1000)
  pcGarden.Print st         'draw in new name
  oldCenter = st            'remember this name so we can erase
End Sub

Creating an Abstract Factory Using VB7

The same GardenMaker program differs substantially in VB7. While it is certainly possible to write Garden as an interface and have each of the derived gardens implement that interface, it is easier to have some of the methods in the base Garden class and the rest in the derived classes.

The other major differences in VB7 have to do with the event system. In VB7, you do not draw on the screen directly from your code. Instead, the screen is updated when the next OnPaint event occurs, and you must tell the paint routine what objects it can now paint.

Since each garden should know how to draw itself, it should have a draw method that draws the appropriate plant names on the garden screen. And since we provided push buttons to draw each of the types of plants, we need to set a Boolean that indicates that you can now draw each of these plant types.

We start with our simplified Plant class, where we pass in the name of the plant right in the constructor.

Public Class Plant
    Private plantName As String
    '-----
    Public Sub New(ByVal nm As String)
        MyBase.New()
        plantName = nm        'save the plant name
    End Sub
    '-----
    Public Function getName() As String
        getName = plantName   'return the plant name
    End Function
End Class

Then we create the basic Garden class, which contained the getShade, get Center, and getBorder methods in the original implementation. We don't need these methods in this implementation because the Garden itself does the drawing.

Public Class Garden
    'protected objects are accessed by derived classes
    Protected pltShade, pltBorder, pltCenter As Plant
    Protected center, shade, border As Boolean

    'These are created in the constructor
    Private gbrush As SolidBrush
    Private gdFont As Font

    'Constructor creates brush and font fro drawing
    Public Sub New()
        MyBase.New()
        gBrush = New SolidBrush(Color.Black)
        gdFont = New Font("Arial", 10)
End Sub
'-----

The drawing is done in a simple draw method where we check if we should draw each kind of plant name and draw it if that Boolean is true.

Public Sub draw(ByVal g As Graphics)
        If border Then
            g.DrawString(pltBorder.getName, gdFont, _
                gbrush, 50, 150)
        End If
        If center Then
            g.DrawString(pltCenter.getName, gdFont, _
                 gbrush, 100, 100)
        End If
        If shade Then
            g.DrawString(pltShade.getName, gdFont,_
                 gbrush, 10, 50)
        End If
End Sub

Then we add three set methods to indicate that you can draw each plant.

Public Sub showCenter()
    center = True
End Sub
'-----
Public Sub showBorder()
    border = True
End Sub
'-----
Public Sub showShade()
    shade = True
End Sub
'-----
Public Sub clear()
    center = False
    border = False
    shade = False
End Sub

Now the three derived classes for the three gardens are extremely simple, and they only contain calls to the constructors for the three plants. The following is the entire AnnualGarden class.

Public Class AnnualGarden
    Inherits Garden
    Public Sub New()
        MyBase.New()
        pltShade = New Plant("Coleus")
        pltBorder = New Plant("Alyssum")
        pltCenter = New Plant("Marigold")
    End Sub
End Class

Note that the plant names are now set in their constructors and that the three plant variables that we set are part of the base garden class.

The PictureBox

We draw the circle representing the shady area inside the PictureBox and draw the names of the plants inside this box as well. Thus, we need to add an OnPaint method not to the main GardenMaker window class but to the PictureBox it contains. One way to do this is by creating a subclass of PictureBox that contains the paint method including the circle drawing and tells the garden to draw itself.

Public Class GdPic
    Inherits System.Windows.Forms.PictureBox
    Private gden As Garden
    Private loaded As Boolean
    Private br As SolidBrush
    '-----
    Private Sub init()
        br = New SolidBrush(Color.Gray)
    End Sub
    '-----
    Public Sub setGarden(ByVal gd As Garden)
        gden = gd
        gden.clear()
        Refresh()
        loaded = True
    End Sub
    '-----
    Protected Overrides Sub OnPaint( _
       ByVal pe As System.Windows.Forms.PaintEventArgs)
        Dim g As Graphics = pe.Graphics
        'draw the circle
        g.FillEllipse(br, 5, 5, 100, 100)
        If loaded Then
            'have the garden draw itself
            gden.draw(g)
        End If
    End Sub
End Class

Note that we do not have to erase the plant name text each time in VB7 because Paint is only called when the whole picture needs to be repainted.

Handling the RadioButton and Button Events

When one of the three radio buttons is clicked, you create a new garden of the correct type and pass it into the picture box class.

Private Sub opPerennial_CheckedChanged( _
            ByVal sender As System.Object, _
            ByVal e As System.EventArgs) _
            Handles opPerennial.CheckedChanged
        gden = New PerennialGarden()
        pbox.setGarden(gden)
End Sub

When you click on one of the buttons to show the plant names, you simply call that garden's method to show that plant name and then call the picture box's Refresh method to cause it to repaint.

Protected Sub ckBorder_CheckedChanged( _
              ByVal sender As Object, _
              ByVal e As System.EventArgs)
        gden.showBorder()
        pBox.refresh()
End Sub

The final VB7 Gardener class is shown in Figure 11-3, and the UML diagram in Figure 11-4.

The VB7 Gardener program.

Figure 11-3. The VB7 Gardener program.

The UML diagram for the VB7 Gardener program.

Figure 11-4. The UML diagram for the VB7 Gardener program.

Adding More Classes

One of the great strengths of the Abstract Factory is that you can add new subclasses very easily. For example, if you needed a GrassGarden or a WildFlowerGarden, you can subclass Garden and produce these classes. The only real change you'd need to make in any existing code is to add some way to choose these new kinds of gardens.

Consequences of Abstract Factory

One of the main purposes of the Abstract Factory is that it isolates the concrete classes that are generated. The actual class names of these classes are hidden in the factory and need not be known at the client level at all.

Because of the isolation of classes, you can change or interchange these product class families freely. Further, since you generate only one kind of concrete class, this system keeps you from inadvertently using classes from different families of products. However, it is some effort to add new class families, since you need to define new, unambiguous conditions that cause such a new family of classes to be returned.

While all of the classes that the Abstract Factory generates have the same base class, there is nothing to prevent some subclasses from having additional methods that differ from the methods of other classes. For example, a BonsaiGarden class might have a Height or WateringFrequency method that is not in other classes. This presents the same problem that occurs in any subclass: You don't know whether you can call a class method unless you know whether the subclass is one that allows those methods. This problem has the same two solutions as in any similar case: You can either define all of the methods in the base class, even if they don't always have an actual function, or, you can derive a new base interface that contains all the methods you need and subclass that for all of your garden types.

Thought Question

Thought Question

If you are writing a program to track investments, such as stocks, bonds, metal futures, derivatives, and the like, how might you use an Abstract Factory?

Programs on the CD-ROM

AbstractFactoryGardenPlanner VB6 version of Gardener program
AbstractFactoryVBNetGardenmaker VB7 version of Gardener program
..................Content has been hidden....................

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