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.
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.
What are good border plants?
What are good center plants?
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.
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.
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
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.
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.
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.
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.
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.
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?