In this article we will learn the Template Design pattern with a real time example
Introduction
Let us first start with a problem/case study because of which I will write this article
Problem statement
There are two sources of data and from those sources, we need to
- form two different email body (Template pattern will act here)
- compose them into a single one
- and then need to send the mail
Well, the statement is not so clear..right?Let us see what we are trying to discuss
First Entity:Exception Report Entity
public class ExceptionReport
{
public int SalesOrderId { get; set; }
public string ResellerPartnerId { get; set; }
public string ResellerName { get; set; }
public string DistributorPartnerId { get; set; }
public string DistributorName { get; set; }
public string ExceptionDescription { get; set; }
}
This is an Exception Report entity where some abnormal/unexpected reports will be stored
Second Entity:PartnerNotRegistered Entity
public class PartnerNotRegistered
{
public int OrderNo { get; set; }
public string PartnerId { get; set; }
public string PartnerType { get; set; }
public string CountryName { get; set; }
}
This is a Partner Not Registered entity where records pertaining to those partners will be stored who are not registereed to the system.
Now, two different email bodies needs to be prepared from the sources
The email content of the first entity (ExceptionReport) will be

The email content of the second entity (PartnerNotRegistered) will be

Then these two needs to be merged and the structure after merging will be

Finally these needs to be emailed to the respective participants
Existing code - The traditional linear way/approach
public class ExistingTradionalWay
{
public void ComposeMergeAndSendMail()
{
try
{
#region Email Composition For Exception Reports
StringBuilder sbEmailMsg1 = new StringBuilder();
//source data
var exceptionSourceRecord = Source.GetExceptionReports();
//Email header
sbEmailMsg1.Append("Exception Report");
sbEmailMsg1.Append("<table width='100%' border='1'>");
sbEmailMsg1.Append("<tr>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append("<b>SalesOrderId</b>");
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append("<b>ResellerPartnerId</b>");
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append("<b>ResellerName</b>");
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append("<b>DistributorPartnerId</b>");
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append("<b>DistributorName<b/>");
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append("<b>ExceptionDescription</b>");
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("</tr>");
//Email body
foreach (ExceptionReport excRecord in exceptionSourceRecord)
{
sbEmailMsg1.Append("<tr>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append(excRecord.SalesOrderId);
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append(excRecord.ResellerPartnerId);
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append(excRecord.ResellerName);
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append(excRecord.DistributorPartnerId);
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append(excRecord.DistributorName);
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("<td>");
sbEmailMsg1.Append(excRecord.ExceptionDescription);
sbEmailMsg1.Append("</td>");
sbEmailMsg1.Append("</tr>");
}
//Email tail
sbEmailMsg1.Append("</table>");
var EmailMsg1 = sbEmailMsg1.ToString();
#endregion
#region Email Composition For Partner Not Registered
StringBuilder sbEmailMsg2 = new StringBuilder();
//source data
var partnerNotRegisteredSourceRecord = Source.GetPartnerNotRegistered();
//Email header
sbEmailMsg2.Append("Partner Not Registered");
sbEmailMsg2.Append("<table width='100%' border='1'>");
sbEmailMsg2.Append("<tr>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append("<b>OrderNo</b>");
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append("<b>PartnerId</b>");
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append("<b>PartnerType</b>");
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append("<b>CountryName</b>");
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("</tr>");
//Email body
foreach (PartnerNotRegistered pnr in partnerNotRegisteredSourceRecord)
{
sbEmailMsg2.Append("<tr>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append(pnr.OrderNo);
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append(pnr.PartnerId);
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append(pnr.PartnerType);
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("<td>");
sbEmailMsg2.Append(pnr.CountryName);
sbEmailMsg2.Append("</td>");
sbEmailMsg2.Append("</tr>");
}
//Email tail
sbEmailMsg2.Append("</table>");
var EmailMsg2 = sbEmailMsg2.ToString();
#endregion
#region Compose both the email contents
var emailList = new string[] { EmailMsg1, EmailMsg2 };
StringBuilder sbEmailContent = new StringBuilder();
for (int i = 0; i < emailList.Length; i++)
{
sbEmailContent.AppendLine(emailList[i]);
}
var composedEmailContent = sbEmailContent.ToString();
#endregion
#region Send the mail
SendMail(composedEmailContent);
#endregion
}
catch (Exception ex)
{
throw ex;
}
}
private void SendMail(string composedEmailContent)
{
try
{
SmtpClient mailClient = new SmtpClient();
MailMessage mailMessage = new MailMessage("from@from.com", "to@to.com");
mailClient.Host = "localhost";
mailMessage.Subject = "Composed Email";
mailMessage.Body = composedEmailContent;
mailMessage.IsBodyHtml = true;
mailClient.Send(mailMessage);
}
catch (Exception ex)
{
throw ex;
}
}
}
This program works fine but look into the current implementation.There are some drawbacks
- Difficult to maintain
- Difficult to extend the existing functionality
- A single class has been asked to perform multiple tasks(Prepare individual mail content, Compose them, Send them)
- Not at all suitable for testing
- In future if more entities participate, it will be difficult to code, test etc.
- Duplication of code/pattern
- Does not satisfy SOLID OO Design principle
Rescuer
Template design pattern.
What is Template design pattern?
It provides an implementation in a derived class that needs to be used by the base class.It comes under the category of Behavioral Patterns.
Pattern Components
The template pattern comprises the following components:
- AbstractClass:It defines an abstract and a non-abstract method
- ConcreteClass: It overrides the behaviour of the abstract method
UML Class diagram

Can we see the step by step implementation?
Definitely.First of all let us create an abstract class by the name "EmailTemplate.cs"
abstract class EmailTemplate
{
public abstract string GetEmailContent();
public object SourceType { get; set; }
public string GetEmailTemplate()
{
return GetEmailContent();
}
}
This class has an abstract method GetEmailContent() that will be implemented by the concrete classes. There is a non-abstract method, that will be impleneted in the client class.Also the client class has to register the data souce for that to pass to the respective concreate classes.Henceforth, we have the SourceType property.
Then create the first concreate class "ExceptionReportEmailTemplate.cs" that implements the "EmailTemplate.cs" abstract class and override it's GetEmailContent() method
class ExceptionReportEmailTemplate : EmailTemplate
{
StringBuilder sbEmailMsg;
List<ExceptionReport> lstExceptionReport;
public override string GetEmailContent()
{
sbEmailMsg = new StringBuilder();
lstExceptionReport = SourceType as List<ExceptionReport>;
ComposeEmail();
return sbEmailMsg.ToString();
}
#region Private methods
private void ComposeEmail()
{
EmailHeader();
EmailBody();
EmailFooter();
}
private void EmailHeader()
{
sbEmailMsg.Append("Exception Report");
sbEmailMsg.Append("<table width='100%' border='1'>");
sbEmailMsg.Append("<tr>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("<b>SalesOrderId</b>");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("<b>ResellerPartnerId</b>");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("<b>ResellerName</b>");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("<b>DistributorPartnerId</b>");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("<b>DistributorName<b/>");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("<b>ExceptionDescription</b>");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("</tr>");
}
private void EmailBody()
{
foreach (ExceptionReport excRecord in lstExceptionReport)
{
sbEmailMsg.Append("<tr>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(excRecord.SalesOrderId);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(excRecord.ResellerPartnerId);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(excRecord.ResellerName);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(excRecord.DistributorPartnerId);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(excRecord.DistributorName);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(excRecord.ExceptionDescription);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("</tr>");
}
}
private void EmailFooter()
{
sbEmailMsg.Append("</table>");
}
#endregion
public List<ExceptionReport> SourceType { get; set; }
}
A similar kind of implementation has been done for "PartnerNotRegisterEmailTemplate.cs" class
class PartnerNotRegisterEmailTemplate : EmailTemplate
{
StringBuilder sbEmailMsg;
List<PartnerNotRegistered> lstPartnerNotRegistered;
public override string GetEmailContent()
{
sbEmailMsg = new StringBuilder();
lstPartnerNotRegistered = SourceType as List<PartnerNotRegistered>;
ComposeEmail();
return sbEmailMsg.ToString();
}
#region Private methods
private void ComposeEmail()
{
EmailHeader();
EmailBody();
EmailFooter();
}
private void EmailHeader()
{
sbEmailMsg.Append("Partner Not Registered");
sbEmailMsg.Append("<table width='100%' border='1'>");
sbEmailMsg.Append("<tr>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("OrderNo");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("PartnerId");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("PartnerType");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append("CountryName");
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("</tr>");
}
private void EmailBody()
{
foreach (PartnerNotRegistered pnr in lstPartnerNotRegistered)
{
sbEmailMsg.Append("<tr>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(pnr.OrderNo);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(pnr.PartnerId);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(pnr.PartnerType);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("<td>");
sbEmailMsg.Append(pnr.CountryName);
sbEmailMsg.Append("</td>");
sbEmailMsg.Append("</tr>");
}
}
private void EmailFooter()
{
sbEmailMsg.Append("</table>");
}
#endregion
}
At this stage we can figure out that if some more concrete requirement comes in future then we can easily handle that by creating another concrete class and they are entirely loosely coupled, concentrate on their own way of implementation and is responsible for their own unit.It helps to easily test also the individual modules
Now let us have a client class (say ClientApplication.cs) that will interact with the concrete classes, will compose the emails and later send
class ClientApplication
{
public void ComposeMergeAndSendMail()
{
var exceptionReportEamilTemplate = GetEmailcontentForExceptionReport();
var partnerNotRegisterEmailTemplate = GetEmailcontentForPartnerNotRegister();
//Compose mails
var composedEmails = ComposeEmail.ComposeEmails(new string[] { exceptionReportEamilTemplate, partnerNotRegisterEmailTemplate });
//Send the email
EmailSender.SendMail(composedEmails);
}
private string GetEmailcontentForExceptionReport()
{
//source data
List<ExceptionReport> lstExceptionReport = Source.GetExceptionReports();
string exceptionReportEamilTemplate = string.Empty;
if (lstExceptionReport.Count > 0)
{
var template = new ExceptionReportEmailTemplate();
template.SourceType = lstExceptionReport; //assign the Source type
exceptionReportEamilTemplate = template.GetEmailTemplate(); //Get request specific email template.
}
return exceptionReportEamilTemplate;
}
private string GetEmailcontentForPartnerNotRegister()
{
//source data
List<PartnerNotRegistered> lstPartnerNotRegistered = Source.GetPartnerNotRegistered();
string partnerNotRegisterEmailTemplate = string.Empty;
if (lstPartnerNotRegistered.Count > 0)
{
var template = new PartnerNotRegisterEmailTemplate();
template.SourceType = lstPartnerNotRegistered;//assign the Source type
partnerNotRegisterEmailTemplate = template.GetEmailTemplate(); //Get request specific email template.
}
return partnerNotRegisterEmailTemplate;
}
}
As we can make out that the code is very much cleaner.Here every class is performing only one responsibility. The "ComposeEmail.cs" file is meant for only composition of emails and "EmailSender.cs" is responsible for for Sending emails
The code for "ComposeEmail.cs" is as under
public class ComposeEmail
{
public static string ComposeEmails(params string[] emailList)
{
StringBuilder sbEmailContent = new StringBuilder();
var cnt = emailList.Length;
for (int i = 0; i < cnt; i++)
{
sbEmailContent.AppendLine(emailList[i]);
}
return sbEmailContent.ToString();
}
}
The code for "EmailSender.cs" is as under
public static void SendMail(string mailContent)
{
try
{
SmtpClient mailClient = new SmtpClient();
MailMessage mailMessage = new MailMessage("from@from.com", "to@to.com");
mailClient.Host = "localhost";
mailMessage.Subject = "Composed Email";
mailMessage.Body = mailContent;
mailMessage.IsBodyHtml = true;
mailClient.Send(mailMessage);
}
catch (Exception ex)
{
throw ex;
}
}
N.B.~One more note to pass that is the "ComposeEmail.cs" and "EmailSender.cs" are not part of the Template Design Pattern.It has been included to show the entire flow so that new comers can appretiate it's usage in real time scenario
Advantages of the scenario after implementation of the Template Pattern
- Every class is responsible of their of unit of activity
- Easily maintainable
- Easy to test
- Compeltely loosely coupled
- Can be easily extended without dependency of any of the existing code
- Follows SOLID design principle
References
- Template method pattern
- Template Method
Conclusion
Hope this article has given a good insight as when to use template pattern , how to design using SOLID principle etc.Comments are highly appreciated.Zip file is attached
Disclaimer~This is a real time problem I recently encounter.Here I have proposed my approach of solving the problem.There can be other ways also of doing so.Users are free to apply their own thoughts.However, this article is meant basically for teaching the purpose of implementation of template pattern.