Chapter 29. 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 29-1.

Figure 29-1. Two instances of a PlotStrategy class

image

Our base PlotStrategy class is an abstract class 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.


public abstract class PlotStrategy     {
      public abstract void plot( float[] x, float[] y);
}

Then one 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 set a variable to refer to one concrete strategy or another.


public class Context      {
      float[] x, y;
      PlotStrategy plts;  //strategy selected goes here
      //-----
      public void plot() {
             readFile();         //read in data
             plts.plot (x, y);
      }
      //-----
      //select bar plot
      public void setBarPlot() {
             plts = new BarPlotStrategy ();
      }
      //-----
      //select line plot
      public void setLinePlot() {
             plts = new LinePlotStrategy();
      }
      //-----
      public void readFile()           {
      //reads data in from data file
      }
}

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 29-2) is just a panel with two buttons that call the two plots. Each of the buttons is a derived Button class that implements the Command interface. It selects the correct strategy and then calls the Context’s plot routine. For example, here is the complete Line graph command Button class.

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

image


public class LineButton : System.Windows.Forms.Button, Command
{
      private System.ComponentModel.Container components = null;
      private Context contxt;

      public LineButton()   {
             InitializeComponent();
             this.Text = "Line plot";
      }
      public void setContext(Context ctx) {
             contxt = ctx;
      }
      public void Execute() {
             contxt.setLinePlot();
             contxt.plot();
      }

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 LinePlotStrategy.


public class LinePlotStrategy : PlotStrategy  {
      public override void plot(float[] x, float[] y) {
             LinePlot lplt = new LinePlot();
             lplt.Show ();
             lplt.plot (x, y);
      }
}

The BarPlotStrategy is more or less identical.

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 void plot(float[] xp, float[] yp) {
      x = xp;
      y = yp;
      setPlotBounds();    //compute scaling factors
      hasData = true;
      pic.Refresh();
}

Drawing Plots in C#

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. Since they can use the same scaling function, we write it once in the BarPlot window and derive the LinePlot window from it to use the same methods.


public virtual void setPlotBounds() {
      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));
}
//-----
public int calcx(float xp) {
      int ix = (int)((xp - xmin) * xfactor + xpmin);
      return ix;
}
//-----
public int calcy(float yp) {
      yp = ((yp - ymin) * yfactor);
      int iy = h - (int)(ypmax - yp);
      return iy;
}

Making Bar Plots

The actual bar plot is drawn in a Paint routine that is called when a paint event occurs.


protected virtual void pic_Paint(object sender, PaintEventArgs e)
{
      Graphics g = e.Graphics;
      if (hasData) {

      for (int i = 0; i< x.Length; i++){
             int ix = calcx(x[i]);
             int iy = calcy(y[i]);
             Brush br = (Brush)colors[i];
             g.FillRectangle(br, ix, h - iy, 20, iy);
      }
}

Making Line Plots

The LinePlot class is very simple, since we derive it from the BarPlot class, and we need only write a new Paint method.


public class LinePlot :BarPlot {
      public LinePlot() {
             bPen = new Pen(Color.Black);
             this.Text = "Line Plot";
      }
      protected override void pic_Paint(object sender,
                    PaintEventArgs e) {
             Graphics g= e.Graphics;
             if (hasData) {
                    for (int i = 1; i< x.Length; i++) {
                           int ix = calcx(x[i - 1]);
                           int iy = calcy(y[i - 1]);
                           int ix1 = calcx(x[i]);
                           int iy1 = calcy(y[i]);
                           g.DrawLine(bPen, ix, iy, ix1, iy1);
                    }
             }
      }
}

The UML diagram of these class relations is shown in Figure 29-3. The final two plots are shown in Figure 29-4.

Figure 29-3. The UML diagram of the Strategy pattern

image

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

image

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 condition 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.

Program on the CD-ROM

image

..................Content has been hidden....................

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