Let us learn Template Design Pattern

Niladri.Biswas
Posted by in Design Pattern & Practices category on for Beginner level | Points: 250 | Views : 9047 red flag
Rating: 5 out of 5  
 3 vote(s)

In this article we will learn the Template Design pattern with a real time example


 Download source code for Let us learn Template Design Pattern

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

  1. form two different email body (Template pattern will act here)
  2. compose them into a single one
  3. 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

  1. Difficult to maintain
  2. Difficult to extend the existing functionality
  3. A single class has been asked to perform multiple tasks(Prepare individual mail content, Compose them, Send them)
  4. Not at all suitable for testing
  5. In future if more entities participate, it will be difficult to code, test etc.
  6. Duplication of code/pattern
  7. 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:

  1. AbstractClass:It defines an abstract and a non-abstract method
  2. 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

  1. Every class is responsible of their of unit of activity
  2. Easily maintainable
  3. Easy to test
  4. Compeltely loosely coupled
  5. Can be easily extended without dependency of any of the existing code
  6. Follows SOLID design principle

References

  1. Template method pattern
  2. 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.

Page copy protected against web site content infringement by Copyscape

About the Author

Niladri.Biswas
Full Name: Niladri Biswas
Member Level: Platinum
Member Status: Member
Member Since: 10/25/2010 11:04:24 AM
Country: India
Best Regards, Niladri Biswas
http://www.dotnetfunda.com
Technical Lead at HCL Technologies

Login to vote for this post.

Comments or Responses

Posted by: Srilu.Nayini577 on: 8/17/2012 | Points: 25
Nice article.Please post more article which is most useful to freshers(<1exp)

Login to post response

Comment using Facebook(Author doesn't get notification)