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. The 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.
Since inheritance is a critical part of this pattern, we will develop our Template Method example exclusively in VB7.
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 MustInherit Class PlotWindow Inherits System.Windows.Forms.Form 'base plot window class for bar and line plots Protected xfactor As Single, xpmin As Single Protected xpmax As Single Protected xmin As Single, xmax As Single Protected ymin As Single, ymax As Single Protected yfactor As Single, ypmin As Single Protected ypmax As Single Protected x() As Single, y() As Single Protected bPen As Pen Protected hasData As Boolean Protected w As Integer, h As Integer Const max As Single = 1E+38 '----- Public Sub setPenColor(ByVal c As Color) bpen = New Pen(c) End Sub '----- Private Sub findBounds() 'on CD-Rom End Sub '----- Public Overridable Sub set_Bounds( _ ByVal pic As PictureBox) findBounds() 'on CD-Rom End Sub '----- Public Function calcx(ByVal xp As Single) As Integer Dim ix As Integer ix = ((xp - xmin) * xfactor + xpmin).ToInt16 Return ix End Function '----- Public Function calcy(ByVal yp As Single) As Integer Dim iy As Integer yp = ((yp - ymin) * yfactor).ToInt16 iy = (ypmax - yp).ToInt16 Return iy End Function '----- Public Sub plot(ByVal xp() As Single, _ ByVal yp() As Single, ByVal pic As PictureBox) x = xp y = yp set_Bounds(pic) 'compute scaling factors hasData = True repaint() End Sub '----- Public MustOverride Sub repaint() '----- End Class
Thus, these methods all belonged in a base PlotPanel class without any actual plotting capabilities. Note that the plot method sets up all the scaling constants and just calls repaint. 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.
As discussed in Design Patterns, the Template Method pattern has four kinds of methods that you can use in derived classes.
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.
Methods that are not filled in at all and must be implemented in derived classes. In VB7, you would declare these as MustOverride methods.
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 VB7 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.
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.
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 31-1.
Our abstract Triangle class illustrates the Template pattern.
Public MustInherit Class Triangle Private p1, p2, p3 As Point Protected bPen As Pen '----- Public Sub New(ByVal a As Point, ByVal b As Point, _ ByVal c As Point) p1 = a p2 = b p3 = c bPen = New Pen(Color.Black) End Sub '----- 'draw the complete triangle Public Sub draw(ByVal g As Graphics) drawLine(g, p1, p2) Dim c As Point = draw2ndLine(g, p2, p3) closeTriangle(g, c) End Sub '----- 'draw one line Public Sub drawLine(ByVal g As Graphics, _ ByVal a As Point, ByRef b As Point) g.drawLine(bpen, a.x, a.y, b.x, b.y) End Sub '----- 'method you must override in derived classes Public MustOverride Function draw2ndLine(_ ByVal g As Graphics, _ ByVal a As Point, ByVal b As Point) As Point '----- 'close by drawing back to beginning Public Sub closeTriangle(ByVal g As Graphics, _ ByVal c As Point) g.DrawLine(bpen, c.X, c.Y, p1.x, p1.y) End Sub End Class
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 case triangle class is to include default code for the draw2ndLine method.
Public Overridable Function draw2ndLine(_ ByVal g As Graphics, ByVal a As point, _ ByVal b As Point) As Point g.DrawLine(bpen, a.X, a.Y, b.X, b.Y) Return b End Function
In this case, the draw2ndLine method becomes a Hook method that can be overridden for other classes.
To draw a general triangle with no restrictions on its shape, we simple implement the draw2ndLine method in a derived stdTriangle class.
Public Class StdTriangle Inherits Triangle '----- Public Sub new(ByVal a As Point, _ ByVal b As Point, ByVal c As Point) MyBase.new(a, b, c) End Sub '----- Public Overrides Function draw2ndLine(_ ByVal g As Graphics, ByVal a As point, _ ByVal b As Point) As Point g.DrawLine(bpen, a.X, a.Y, b.X, b.Y) Return b End Function End Class
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 Inherits Triangle Private newc As Point Private newcx, newcy As Integer '----- Public Sub New(ByVal a As Point, ByVal b As Point, _ ByVal c As Point) MyBase.New(a, b, c) Dim dx1, dy1, dx2, dy2, side1, side2 As Single Dim slope, intercept As Single Dim incr As Integer 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) Then incr = -1 Else incr = 1 End If 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 (abs(side1 - side2) > 1) newcx = newcx + incr 'iterate a pixel at a time until close newcy = (slope * newcx + intercept).ToInt16 dx2 = newcx - b.x dy2 = newcy - b.y side2 = calcSide(dx2, dy2) End While newc = New Point(newcx, newcy) End Sub '----- Private Function calcSide(ByVal dx As Single, _ ByVal dy As Single) As Single Return Sqrt(dx * dx + dy * dy).ToSingle End Function
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.
'draw 2nd line using new saved point Public Overrides Function draw2ndLine( _ ByVal g As Graphics, ByVal b As Point, _ ByVal c As Point) As Point g.DrawLine(bpen, b.X, b.Y, newc.X, newc.Y) Return newc End Function
The main program simply creates instances of the triangles you want to draw. Then it adds them to an ArrayList in the TriangleForm class.
Public Class Form1 Inherits System.Windows.Forms.Form Private triangles As ArrayList Private Sub init() 'Create a list of triangles to draw triangles = New ArrayList() Dim t1 As New StdTriangle( _ New Point(10, 10), New Point(150, 50), _ New Point(100, 75)) Dim t2 As New IsocelesTriangle( _ New Point(150, 100), New Point(240, 40), _ New Point(175, 150)) triangles.Add(t1) triangles.Add(t2) End Sub
It is the paint routine in this class that actually draws the triangles.
Public Sub Pic_Paint(ByVal sender As Object, _ ByVal e As System.WinForms.PaintEventArgs) _ Handles Pic.Paint Dim i As Integer Dim g As Graphics = e.Graphics For i = 0 To triangles.Count - 1 Dim t As Triangle = _ CType(triangles(i), triangle) t.draw(g) Next End Sub
A standard triangle and an isosceles triangle are shown in Figure 31-2.
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.
drawLine(g, p1, p2) Dim c As Point = 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.
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.