Chapter 30. The Template Method Pattern

The Template Method pattern is a very simple pattern that you will find yourself using frequently. Whenever you write a parent class where you leave one or more of the methods to be implemented by derived classes, you are in essence using the Template pattern. T he Template pattern formalizes the idea of defining an algorithm in a class but leaving some of the details to be implemented in subclasses. In other words, if your base class is an abstract class, as often happens in these design patterns, you are using a simple form of the Template pattern.

Motivation

Templates are so fundamental, you have probably used them dozens of times without even thinking about it. The idea behind the Template pattern is that some parts of an algorithm are well defined and can be implemented in the base class, whereas other parts may have several implementations and are best left to derived classes. Another main theme is recognizing that there are some basic parts of a class that can be factored out and put in a base class so they do not need to be repeated in several subclasses.

For example, in developing the BarPlot and LinePlot classes we used in the Strategy pattern examples in the previous chapter, we discovered that in plotting both line graphs and bar charts we needed similar code to scale the data and compute the x and y pixel positions.


public abstract class PlotWindow : Form {
      protected float ymin, ymax, xfactor, yfactor;
      protected float xpmin, xpmax, ypmin, ypmax, xp, yp;
      private float xmin, xmax;
      protected int w, h;
      protected float[] x, y;

      protected Pen bPen;
      protected bool hasData;
      protected const float max = 1.0e38f;
      protected PictureBox pic;
      //-----
      protected virtual void init() {
             pic.Paint += new PaintEventHandler (pic_Paint);
      }
      //-----
      public void setPenColor(Color c){
             bPen = new Pen(c);
      }
      //-----
      public void plot(float[] xp, float[] yp) {
             x = xp;
             y = yp;
             setPlotBounds();    //compute scaling factors
             hasData = true;
      }
      //-----
      public void findBounds() {
             xmin = max;
             xmax = -max;
             ymin = max;
             ymax = -max;
             for (int i = 0; i<  x.Length ; i++) {
                    if (x[i] > xmax) xmax = x[i];
                    if (x[i] < xmin) xmin = x[i];
                    if (y[i] > ymax) ymax = y[i];
                    if (y[i] < ymin) ymin = y[i];
             }
      }
      //-----
      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;
      }
      //-----
      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;
      }
      //-----
      public abstract void repaint(Graphics g) ;
      //-----
      protected virtual void pic_Paint(object sender,
                           PaintEvntArgs e) {
             Graphics g = e.Graphics;
             repaint(g);
      }
}

Thus, these methods all belong in a base PlotPanel class without any actual plotting capabilities. Note that the pic_Paint event handler just calls the abstract repaint method. The actual repaint method is deferred to the derived classes. It is exactly this sort of extension to derived classes that exemplifies the Template Method pattern.

Kinds of Methods in a Template Class

As discussed in Design Patterns, the Template Method pattern has four kinds of methods that you can use in derived classes.

  1. Complete methods that carry out some basic function that all the subclasses will want to use, such as calcx and calcy in the preceding example. These are called concrete methods.
  2. Methods that are not filled in at all and must be implemented in derived classes. In C#, you would declare these as virtual methods.
  3. Methods that contain a default implementation of some operations but that may be overridden in derived classes. These are called hook methods. Of course, this is somewhat arbitrary because in C# you can override any public or protected method in the derived class but hook methods, however, are intended to be overridden, whereas concrete methods are not.
  4. Finally, a Template class may contain methods that themselves call any combination of abstract, hook, and concrete methods. These methods are not intended to be overridden but describe an algorithm without actually implementing its details. Design Patterns refers to these as Template methods.

Sample Code

Let’s consider a simple program for drawing triangles on a screen. We’ll start with an abstract Triangle class and then derive some special triangle types from it, as we see in Figure 30-1.

Figure 30-1. The abstract Triangle class and two of its subclasses

image

Our abstract Triangle class illustrates the Template pattern.


public abstract class Triangle    {
      private Point p1, p2, p3;
      protected Pen pen;
      //-----
      public Triangle(Point a, Point b, Point c) {
             p1 = a;
             p2 =  b;
             p3 = c;
             pen = new Pen(Color.Black , 1);
      }
      //-----
      public virtual void draw(Graphics g) {
             g.DrawLine (pen, p1, p2);
             Point c = draw2ndLine(g, p2, p3);
             closeTriangle(g, c);
      }
      //-----
      public abstract Point draw2ndLine(Graphics g,
                           Point a, Point b);
      //-----
      public void closeTriangle(Graphics g, Point c) {
             g.DrawLine (pen, c, p1);
      }
}

This Triangle class saves the coordinates of three lines, but the draw routine draws only the first and the last lines. The all-important draw2ndLine method that draws a line to the third point is left as an abstract method. That way the derived class can move the third point to create the kind of rectangle you wish to draw.

This is a general example of a class using the Template pattern. The draw method calls two concrete base class methods and one abstract method that must be overridden in any concrete class derived from Triangle.

Another very similar way to implement the Triangle class is to include default code for the draw2ndLine method.


public virtual void draw2ndLine(Graphics g,
                  Point a, Point b) {
      g.drawLine(a, b);
}

In this case, the draw2ndLine method becomes a Hook method that can be overridden for other classes.

Drawing a Standard Triangle

To draw a general triangle with no restrictions on its shape, we simply implement the draw2ndLine method in a derived stdTriangle class.


public class StdTriangle :Triangle      {
      public StdTriangle(Point a, Point b, Point c)
                    : base(a, b, c) {}
      //------
      public override Point draw2ndLine(Graphics g,
                    Point a, Point b) {
             g.DrawLine (pen, a, b);
             return b;
      }
}

Drawing an Isosceles Triangle

This class computes a new third data point that will make the two sides equal in length and saves that new point inside the class.


public class IsoscelesTriangle : Triangle      {
      private Point newc;
      private int newcx, newcy;
      //-----
      public IsoscelesTriangle(Point a, Point b, Point c) :
                  base(a, b, c) {
             float dx1, dy1, dx2, dy2, side1, side2;
             float slope, intercept;
             int incr;
             dx1 = b.X - a.X;
             dy1 = b.Y - a.Y;
             dx2 = c.X  - b.X;
             dy2 = c.Y - b.Y;

             side1 = calcSide(dx1, dy1);
             side2 = calcSide(dx2, dy2);

             if (side2 < side1)
                    incr = -1;
             else
                    incr = 1;
             slope = dy2 / dx2;
             intercept = c.Y - slope * c.X;

             //move point c so that this is an isosceles triangle
             newcx = c.X;
             newcy = c.Y;
             while (Math.Abs (side1 - side2) > 1) {
                    //iterate a pixel at a time until close
                    newcx = newcx + incr;
                    newcy = (int)(slope * newcx + intercept);
                    dx2 = newcx - b.X;
                    dy2 = newcy - b.Y;
                    side2 = calcSide(dx2, dy2);
             }
             newc = new Point(newcx, newcy);
      }
      //-----
      private float calcSide(float a, float b) {
             return (float)Math.Sqrt (a*a +  b*b);
      }
}

When the Triangle class calls the draw method, it calls this new version of draw2ndLine and draws a line to the new third point. Further, it returns that new point to the draw method so it will draw the closing side of the triangle correctly.


public override Point draw2ndLine(Graphics g,
                    Point b, Point  c) {
             g.DrawLine (pen, b, newc);
             return newc;
      }

The Triangle Drawing Program

The main program simply creates instances of the triangles you want to draw. Then it adds them to an ArrayList in the TriangleForm class.


private void init() {
      triangles = new ArrayList();
      StdTriangle t1 = new StdTriangle(new Point(10, 10),
             new Point(150, 50),
             new Point(100, 75));
      IsoscelesTriangle t2 = new IsoscelesTriangle(
             new Point(150, 100), new Point(240, 40),

             new Point(175, 150));
      triangles.Add(t1);
      triangles.Add(t2);
      Pic.Paint+= new PaintEventHandler (TPaint);
}

It is the TPaint method in this class that actually draws the triangles by calling each Triangle’s draw method.


private void TPaint (object sender,
             System.Windows.Forms.PaintEventArgs e) {
      Graphics g = e.Graphics;
      for (int i = 0; i<  triangles.Count ; i++) {
             Triangle t = (Triangle)triangles[i];
             t.draw(g);
      }
}

A standard triangle and an isosceles triangle are shown in Figure 30-2.

Figure 30-2. A standard triangle and an isosceles triangle

image

Templates and Callbacks

Design Patterns points out that Templates can exemplify the “Hollywood Principle,” or “Don’t call us, we’ll call you.” The idea here is that methods in the base class seem to call methods in the derived classes. The operative word here is seem. If we consider the draw code in our base Triangle class, we see that there are three method calls.


g.DrawLine (pen, p1, p2);
Point c = draw2ndLine(g, p2, p3);
closeTriangle(g, c);

Now drawLine and closeTriangle are implemented in the base class. However, as we have seen, the draw2ndLine method is not implemented at all in the base class, and various derived classes can implement it differently. Since the actual methods that are being called are in the derived classes, it appears as though they are being called from the base class.

If this idea makes you uncomfortable, you will probably take solace in recognizing that all the method calls originate from the derived class and that these calls move up the inheritance chain until they find the first class that implements them. If this class is the base class—fine. If not, it could be any other class in between. Now when you call the draw method, the derived class moves up the inheritance tree until it finds an implementation of draw. Likewise, for each method called from within draw, the derived class starts at the current class and moves up the tree to find each method. When it gets to the draw2ndLine method, it finds it immediately in the current class. So it isn’t “really” called from the base class, but it does seem that way.

Summary and Consequences

Template patterns occur all the time in OO software and are neither complex nor obscure in intent. They are a normal part of OO programming, and you shouldn’t try to make them into more than they actually are.

The first significant point is that your base class may only define some of the methods it will be using, leaving the rest to be implemented in the derived classes. The second major point is that there may be methods in the base class that call a sequence of methods, some implemented in the base class and some implemented in the derived class. This Template method defines a general algorithm, although the details may not be worked out completely in the base class.

Template classes will frequently have some abstract methods that you must override in the derived classes, and they may also have some classes with a simple “placeholder” implementation that you are free to override where this is appropriate. If these placeholder classes are called from another method in the base class, then we call these overridable methods “hook” methods.

Programs 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