Chapter 30. The Strategy Pattern

The Strategy pattern is much like the State pattern in outline but a little different in intent. The Strategy pattern consists of a number of related algorithms encapsulated in a driver class called the Context. Your client program can select one of these differing algorithms, or in some cases, the Context might select the best one for you. The intent is to make these algorithms interchangeable and provide a way to choose the most appropriate one. The difference between State and Strategy is that the user generally chooses which of several strategies to apply and that only one strategy at a time is likely to be instantiated and active within the Context class. By contrast, as we have seen, it is possible that all of the different States will be active at once, and switching may occur frequently between them. In addition, Strategy encapsulates several algorithms that do more or less the same thing, whereas State encapsulates related classes that each do something somewhat differently. Finally, the concept of transition between different states is completely missing in the Strategy pattern.

Motivation

A program that requires a particular service or function and that has several ways of carrying out that function is a candidate for the Strategy pattern. Programs choose between these algorithms based on computational efficiency or user choice. There can be any number of strategies, more can be added, and any of them can be changed at any time.

There are a number of cases in programs where we'd like to do the same thing in several different ways. Some of these are listed in the Smalltalk Companion.

  • Save files in different formats.

  • Compress files using different algorithms

  • Capture video data using different compression schemes.

  • Use different line-breaking strategies to display text data.

  • Plot the same data in different formats: line graph, bar chart, or pie chart.

In each case we could imagine the client program telling a driver module (Context) which of these strategies to use and then asking it to carry out the operation.

The idea behind Strategy is to encapsulate the various strategies in a single module and provide a simple interface to allow choice between these strategies. Each of them should have the same programming interface, although they need not all be members of the same class hierarchy. However, they do have to implement the same programming interface.

Sample Code

Let's consider a simplified graphing program that can present data as a line graph or a bar chart. We'll start with an abstract PlotStrategy class and derive the two plotting classes from it, as illustrated in Figure 30-1.

Two instance of a PlotStrategy class

Figure 30-1. Two instance of a PlotStrategy class

Our base PlotStrategy class acts as an interface containing the plot routine to be filled in in the derived strategy classes. It also contains the max and min computation code, which we will use in the derived classes by containing an instance of this class.

'Interface PlotStrategy
Private xmin As Single, xmax As Single
Private ymin As Single, ymax As Single
Const max = 1E+38
Public Sub plot(x() As Single, y() As Single)
'to be filled in
'in implementing classes
End Sub
'-----
Public Sub findBounds(x() As Single, y() As Single)
 Dim i As Integer
 xmin = max
 xmax = -max
 ymin = max
 ymax = -max

 For i = 1 To UBound(x())
   If x(i) > xmax Then xmax = x(i)
   If x(i) < xmin Then xmin = x(i)
   If y(i) > ymax Then ymax = y(i)
   If y(i) < ymin Then ymin = y(i)
 Next i
End Sub
'-----
Public Function getXmax() As Single
 getXmax = xmax
End Function
'-----
Public Function getYmax() As Single
 getYmax = ymax
End Function
'-----
Public Function getXmin() As Single
 getXmin = xmin
End Function
'-----
Public Function getYmin() As Single
 getYmin = ymin
End Function

The important part is that all of the derived classes must implement a method called plot with two float arrays as arguments. Each of these classes can do any kind of plot that is appropriate.

The Context

The Context class is the traffic cop that decides which strategy is to be called. The decision is usually based on a request from the client program, and all that the Context needs to do is to set a variable to refer to one concrete strategy or another.

'Class Context
Dim fl As vbFile
Dim x() As Single, y() As Single
Dim plts As PlotStrategy
'-----
Public Sub setLinePlot()
 Set plts = New LinePlotStrategy
End Sub
'-----
Public Sub setBarPlot()
 Set plts = New BarPlotStrategy
End Sub
'-----
Public Sub plot()
 readFile
 plts.findBounds x(), y()
 plts.plot x(), y()     'do whatever kind of plot
End Sub
'-----
Private Sub readFile()
'reads data in from data file
End Sub

The Context class is also responsible for handling the data. Either it obtains the data from a file or database or it is passed in when the Context is created. Depending on the magnitude of the data, it can either be passed on to the plot strategies or the Context can pass an instance of itself into the plot strategies and provide a public method to fetch the data.

The Program Commands

This simple program (Figure 30-2) is just a panel with two buttons that call the two plots. Each of the buttons is associated with a command object that sets the correct strategy and then calls the Context's plot routine. For example, here is the complete Line graph command class.

A simple panel to call two different plots

Figure 30-2. A simple panel to call two different plots

'Class LineCmd
Implements Command
Private contxt As Context
'-----
Public Sub init(cont As Context)
 Set contxt = cont
End Sub
'-----
Private Sub Command_Execute()
 contxt.setLinePlot
 contxt.plot
End Sub

The Line and Bar Graph Strategies

The two strategy classes are pretty much the same: They set up the window size for plotting and call a plot method specific for that display panel. Here is the Line graph Strategy.

'Class LinePlotStrategy
Implements PlotStrategy
Dim plts As PlotStrategy

Private Sub Class_Initialize()
 'base class used to compute bounds
 Set plts = New PlotStrategy
End Sub

Private Sub PlotStrategy_findBounds(x() As Single, y() As Single)
 plts.findBounds x, y
End Sub
'-----
'not used in derived classes
Private Function PlotStrategy_getXmax() As Single
End Function
Private Function PlotStrategy_getXmin() As Single
End Function
Private Function PlotStrategy_getYmax() As Single
End Function
Private Function PlotStrategy_getYmin() As Single
End Function
'-----
Private Sub PlotStrategy_plot(x() As Single, y() As Single)
Dim lplot As New LinePlot
 plts.findBounds x, y
 lplot.setBounds plts.getXmin, _
        plts.getXmax, plts.getYmin, plts.getYmax
 lplot.Show
 lplot.plot x(), y()
End Sub

Drawing Plots in VB

Note that both the LinePlot and the BarPlot window have plot methods that are called by the plot methods of the LinePlotStrategy and BarPlotStrategy classes. Both plot windows have a setBounds method that computes the scaling between the window coordinates and the x-y coordinate scheme.

Public Sub setBounds(xmn As Single, xmx As Single, ymn As Single, _
                     ymx As Single)
 xmax = xmx
 xmin = xmn
 ymax = ymx
 ymin = ymn
 h = Pic.Height
 w = Pic.Width
 xfactor = 0.9 * w / (xmax - xmin)
 xpmin = 0.05 * w
 xpmax = w - xpmin

 yfactor = 0.9 * h / (ymax - ymin)
 ypmin = 0.05 * h
 ypmax = h - ypmin
 bounds = True
End Sub

In VB6 you use the Line command to draw both the line and the bar plots. However, these plotting commands are immediate and do not refresh the screen if a window is obscured and needs to be redrawn. So we save the references to the x and y arrays and also call the plot method from the PictureBox's paint event.

Public Sub plot(xp() As Single, yp() As Single)
 Dim i As Integer, ix As Integer, iy As Integer
 'draw a line plot
 x = xp
 y = yp
 ix = calcx(x(1))
 iy = calcy(y(1))
 Pic.Cls           'clear the picture
 Pic.PSet (ix, iy)  'start the drawing point
'draw the lines
 For i = 2 To UBound(x())
   ix = calcx(x(i))
   iy = calcy(y(i))
   Pic.Line -(ix, iy), vbBlack
 Next i
End Sub
'------
Private Function calcx(ByVal xp As Single) As Integer
 Dim ix As Integer
 ix = (xp - xmin) * xfactor + xpmin
 calcx = ix
End Function
'------
Private Function calcy(ByVal yp As Single) As Integer
 Dim iy As Integer
 yp = (yp - ymin) * yfactor
 iy = ypmax - yp
 calcy = iy
End Function
'------
Private Sub Pic_Paint()
 plot x(), y()
End Sub

The UML diagram showing these class relations is shown in Figure 30-3.

The UML Diagram for the VB6 Strategy pattern

Figure 30-3. The UML Diagram for the VB6 Strategy pattern

The final two plots are shown in Figure 30-4.

The line graph (left) and the bar graph (right)

Figure 30-4. The line graph (left) and the bar graph (right)

The class diagram is given in Figure 30-5.

The UML class diagram for the PlotStrategy classes. Note that we again use the Command pattern.

Figure 30-5. The UML class diagram for the PlotStrategy classes. Note that we again use the Command pattern.

A Strategy Pattern in VB.NET

The VB7 version of Strategy differs primarily in that we do not need to duplicate code between the two Strategies or the two windows, since we can use inheritance to make the same code work for both strategies. We define our basic PlotStrategy class as an empty class that must be overridden.

Public MustInherit Class PlotStrategy
    Public MustOverride Sub plot(ByVal x() As Single, _
               ByVal y() As Single)
End Class

The two instances for LinePlotStrategy and BarPlotStrategy differ only in the plot window they create. Here is the LinePlotStrategy.

Public Class LinePlotStrategy
    Inherits PlotStrategy
    Public Overrides Sub plot(ByVal x() As Single, _
                      ByVal y() As Single)
        Dim lplot As New LinePlot()
        lplot.Show()
        lplot.plot(x, y)
    End Sub
End Class

And here is the BarPlotStrategy.

Public Class BarPlotStrategy
    Inherits PlotStrategy
    Public Overrides Sub plot(ByVal x() As Single, _
                             ByVal y() As Single)
        Dim bplot As New BarPlot()
        bplot.Show()
        bplot.plot(x, y)
    End Sub
End Class

All of the scaling computations can then be housed in one of the plot window classes and inherited for the other. We chose the BarPlot window as the base class, but either one would work as well as the other as the base. This class contains the scaling routines and creates an array of SolidBrush objects for the various colors to be used in the bar plot.

Public Overridable Sub set_Bounds()
        findBounds()
        'compute scaling factors
        h = Pic.Height
        w = Pic.Width
        xfactor = 0.8F * w / (xmax - xmin)
        xpmin = 0.05F * w
        xpmax = w - xpmin

        yfactor = 0.9F * h / (ymax - ymin)
        ypmin = 0.05F * h
        ypmax = h - ypmin
        'create array of colors for bars
        colors = New arraylist()
        colors.Add(New SolidBrush(Color.Red))
        colors.Add(New SolidBrush(color.Green))
        colors.Add(New SolidBrush(color.Blue))
        colors.Add(New SolidBrush(Color.Magenta))
        colors.Add(New SolidBrush(color.Yellow))
    End Sub

The plotting amounts to copying in a reference to the x and y arrays, calling the scaling routine and then causing the Picturebox control to be refreshed, which will then call the paint routine to paint the bars.

    Public Sub plot(ByVal xp() As Single, _
                    ByVal yp() As Single)
        x = xp
        y = yp
        set_Bounds()    'compute scaling factors
        hasData = True
        pic.Refresh()
    End Sub
'-----
    Public Overridable Sub Pic_Paint(_
            ByVal sender As Object, _
            ByVal e As PaintEventArgs) Handles Pic.Paint
        Dim g As Graphics = e.Graphics
        Dim i, ix, iy As Integer
        Dim br As Brush
        If hasData Then
            For i = 0 To x.Length - 1
                ix = calcx(x(i))
                iy = calcy(y(i))
                br = CType(colors(i), brush)
                g.FillRectangle(br, ix, h - iy, 20, iy)
            Next
        End If
    End Sub

The LinePlot window is much simpler now because we can derive it from the BarPlot window and reuse nearly all the code.

Public Class LinePlot
    Inherits BarPlot
    Private bPen As Pen
    Public Sub New()
        MyBase.New
        LinePlot = Me
        InitializeComponent()
        bpen = New Pen(Color.Black)
    End Sub

Public Overrides Sub Pic_Paint(ByVal sender As Object, _
     ByVal e As PaintEventArgs) Handles Pic.Paint
        Dim g As Graphics = e.Graphics
        Dim i, ix, iy, ix1, iy1 As Integer
        Dim br As Brush
        If hasData Then
            For i = 1 To x.Length - 1
                ix = calcx(x(i - 1))
                iy = calcy(y(i - 1))
                ix1 = calcx(x(i))
                iy1 = calcy(y(i))
                g.drawline(bpen, ix, iy, ix1, iy1)
            Next
        End If
    End Sub
End Class

The two buttons are now command buttons whose Execute methods set the context and carry out the requisite plot. Here is the complete Line but ton code:

Public Class LineBtn
    Inherits System.Windows.Forms.Button
    Implements Command
    Private contxt As Context
    '-----
    Public Sub setContext(ByVal ctx As Context)
        contxt = ctx
    End Sub
    '-----
    Public Sub Execute() Implements vbnetStrategy.Command.Execute
        contxt.setLinePlot()
        contxt.plot()
    End Sub
End Class

The two resulting plot windows are identical to those drawn in the VB6 version (Figure 30-4).

Consequences of the Strategy Pattern

Strategy allows you to select one of several algorithms dynamically. These algorithms can be related in an inheritance hierarchy, or they can be unrelated as long as they implement a common interface. Since the Context switches between strategies at your request, you have more flexibility than if you simply called the desired derived class. This approach also avoids the sort of conditional statements that can make code hard to read and maintain.

On the other hand, strategies don't hide everything. The client code is usually aware that there are a number of alternative strategies, and it has some criteria for choosing among them. This shifts an algorithmic decision to the client programmer or the user.

Since there are a number of different parameters that you might pass to different algorithms, you have to develop a Context interface and strategy methods that are broad enough to allow for passing in parameters that are not used by that particular algorithm. For example the setPenColor method in our PlotStrategy is actually only used by the LineGraph strategy. It is ignored by the BarGraph strategy, since it sets up its own list of colors for the successive bars it draws.

Programs on the CD-ROM

Strategy VB6 plot strategy
StrategyVBNetStrategy VB7 plot strategy
..................Content has been hidden....................

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