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()