Design patterns – Part 7: Template method pattern
In this article, I would like to present you a design pattern that is not so common, but for sure, I wish, that in the past I would have used it. It would certainly make my life much easier. So, what is this Template method pattern all about?
The Template Method Pattern defines the skeleton of a an algorithm in one method. Some steps are deferred to subclasses, which are allowed to alter certain algorithm steps without changing the skeleton of an algorithm.
Say what?
Well, to simplify the definition, let’s look at an example. Imagine you have several product types. Your assigned task was to build a product catalogue on the web. All product types will use tables for representation, however not all products can be displayed the same. This is a perfect task for Template method pattern.
But why?
Think about it. Your catalogue needs to be displayed differently for each product, meaning you need to get different sets of product for each display. I agree, you could have written multiple agents, or just use multiple methods in an agent, but think about maintainability. And on top of it all, to display each product type, your algorithm is the same. First you need to do some initialization, then you need to select desired products. Next step is to process document set to create a display. In the end, all you need to do is print the results and terminate (or perform a clean up). Steps select and process are product type dependant. All other steps of the algorithm are common.
Example
For example let’s pretend that we have two types of products: regular classroom courses and e-courses. Regular classroom courses (or just courses) are scheduled courses, while e-courses are actually a 1 month subscription to some content (like video, PDF, etc.).
First, we need to create some storage classes: CProduct and CPrice, that will store information about products. This is more efficient than just have a collection of NotesDocuments.
Class CPrice
Public strDesc As String
Public dPrice As Double
End Class
Class CProduct
m_strName As String
m_strType As String
Public m_Price() As CPrice
Property Get Label As String
Label = Me.m_strName
End Property
Property Get MyType As String
MyType = Me.m_strType
End Property
Sub New (strName As String, strType As String)
Me.m_strName = strName
Me.m_strType = strType
Redim Me.m_Price (0)
End Sub
Sub AddPrice (strDesc As String, dPrice As Double)
Dim nIndex As Integer
nIndex = 0
If (Not Me.m_Price (0) Is Nothing) Then
nIndex = Ubound (Me.m_Price) + 1
End If
Redim Preserve Me.m_price (nIndex)
Set Me.m_price (nIndex) = New CPrice
Me.m_Price (nIndex).strDesc = strDesc
Me.m_Price (nIndex).dPrice = dPrice
End Sub
End Class
Class CPrice contains a description of price (subscription or schedule date) and the price itself. Class CProduct defines a product. Each product can have multiple prices.
Now, to our abstract class that will define the algorithm and required methods.
Class CTemplateProduct m_strHtml As String m_products() As CProduct Sub Initialize() Redim m_products (0) End Sub Sub Select() End Sub Sub Process() End Sub Sub Print() Print m_strHtml End Sub Sub Terminate() Erase m_products End Sub Sub Run() Call Me.Initialize() Call Me.Select() Call Me.Process() Call Me.Print() Call Me.Terminate() End Sub End Class
As you can see, methods Initialize, Print and Terminate are already implemented.
Next, we need to create classes for each product type. These classes will inherit from the abstract class.
Class CTemplateCourse As CTemplateProduct
Sub Select()
Redim Preserve Me.m_products (1)
Set Me.m_products (0) =
New CProduct ("Course 1", "Lecture")
Call Me.m_products (0).AddPrice ("Feb 3rd, 2009", 1000.0)
Set Me.m_products (1) =
New CProduct ("Exam 1", "Lecture & exam")
Call Me.m_products (1).AddPrice ("Feb 20th, 2009", 2000.0)
End Sub
Sub Process()
Dim n As Integer
Dim nPrice As Integer
Dim nLBound As Integer
Dim nUBound As Integer
Me.m_strHtml = {<table border="0">}
For n = Lbound (Me.m_products) To Ubound (Me.m_products)
Me.m_strHtml = Me.m_strHtml & {<tbody><tr>}
Me.m_strHtml = Me.m_strHtml & {<td colspan="2">}
Me.m_strHtml = Me.m_strHtml & _
Me.m_products (n).Label & { - }
Me.m_strHtml = Me.m_strHtml & _
Me.m_products (n).MyType & {</td>}
Me.m_strHtml = Me.m_strHtml & {</tr>}
Me.m_strHtml = Me.m_strHtml & {<tr>}
nLBound = Lbound(Me.m_products (n).m_Price)
nUBound = Ubound (Me.m_products (n).m_Price)
For nPrice = nLBound To nUBound
Me.m_strHtml = Me.m_strHtml & {<td>}
Me.m_strHtml = Me.m_strHtml & _
Me.m_products (n).m_Price (nPrice).strDesc
Me.m_strHtml = Me.m_strHtml & {</td>}
Me.m_strHtml = Me.m_strHtml & {<td>}
Me.m_strHtml = Me.m_strHtml & _
Me.m_products (n).m_Price (nPrice).dPrice
Me.m_strHtml = Me.m_strHtml & {</td>}
Next
Me.m_strHtml = Me.m_strHtml & {</tr>}
Next
Me.m_strHtml = Me.m_strHtml & {</tbody>}
End Sub
End Class
Class CTemplateECourse As CTemplateProduct
Sub Select()
Redim Preserve Me.m_products (1)
Set Me.m_products (0) =
New CProduct ("E-Course 1", "eBook")
Call Me.m_products (0).AddPrice ("1 month", 100.0)
Set Me.m_products (1) =
New CProduct ("E-Course 2", "ePresentation")
Call Me.m_products (1).AddPrice ("1 month", 200.0)
End Sub
Sub Process()
Dim n As Integer
Dim nPrice As Integer
Dim nLBound As Integer
Dim nUBound As Integer
Me.m_strHtml = {<table border="0">}
For n = Lbound (Me.m_products) To Ubound (Me.m_products)
Me.m_strHtml = Me.m_strHtml & {<tbody><tr>}
Me.m_strHtml = Me.m_strHtml & {<td>} &_
Me.m_products (n).Label & {</td>}
Me.m_strHtml = Me.m_strHtml & {<td>} & _
Me.m_products (n).MyType & {</td>}
nLBound = Lbound(Me.m_products (n).m_Price)
nUBound = Ubound (Me.m_products (n).m_Price)
For nPrice = nLBound To nUBound
Me.m_strHtml = Me.m_strHtml & {<td>} & _
Me.m_products (n).m_Price (nPrice).strDesc
Me.m_strHtml = Me.m_strHtml & {<br/>} & _
Me.m_products (n).m_Price (nPrice).dPrice & {</td>}
Next
Me.m_strHtml = Me.m_strHtml & {</tr>}
Next
Me.m_strHtml = Me.m_strHtml & {</tbody></table>}
End Sub
End Class
As you can no doubt see, I have simplified the Select method (this is due to me being to lazy to actually create a form, a view and some 6 documents). Also, method Process creates different HTML for each product type.
Interesting thing, this is about it.
All you have to do is some code that will use this. Here is my test agent, that should be run via browser.
Sub Initialize Dim course As CTemplateProduct Dim eCourse As CTemplateProduct Set course = New CTemplateCourse () Call course.Run() Set eCourse = New CTemplateECourse() Call eCourse.Run() End Sub
August 26th, 2011 at 16:26
This is actually quite useful pattern! One thing is that it is integrating very nicely with the factory design pattern; simply add a function that returns the correct class:
Function getTemplateCourse(templateName As String) As Variant
If templateName = “ECourse” Then
Set getTemplateCourse = New TemplateECourse()
Else
Set getTemplateCourse = New TemplateCourse()
End If
End Function
and then in the agent, You simply go like:
Dim course As TemplateCourse
Set course = getTemplateCourse(“ECourse”)
Call course.Run()
Set course = getTemplateCourse(“Other”)
Call course.Run()