Design pattern in .NET: template method
GoF's classical design pattern book has discussed template method pattern: define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
We see this pattern in .NET programming also:
1. The dispose pattern
Any type that wraps native resource needs to have a finalizer method to release the native resource. It is also recommended for this kind of types to implement the dispose pattern so the users can explicitly call the dispose method to release the native resource deterministically. This dispose pattern follows the template method pattern actually:
Sample code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DesignPattern
{
internal class MyNativeResourceWrapper: IDisposable
{
//template method, which calls the actual implementation method to
//do the cleanup, the actual implementation method can be overrided
//by derived classes.
public void Dispose()
{
Dispose(true);
}
//actual implementation method to do the native resource cleanup
//it is virtual and protected so the derived classes can override
//if the class is sealed, this class should be private
protected virtual void Dispose(bool disposing)
{
if(disposing)
{
//method is called by Dispose(), do the proper cleanup here
//also call GC.SuppressFinaliz so GC will not call us again
GC.SuppressFinalize(this);
}
else
{
//method is called by GC, you should not reference
//other managed members here
//since they may be garbage collected already
}
}
~MyNativeResourceWrapper()
{
Dispose(false);
}
}
class MyDerivedNRW : MyNativeResourceWrapper
{
protected override void Dispose(bool disposing)
{
//Console.WriteLine("Derived Dispose");
base.Dispose(disposing);
}
}
class Program
{
static void Main(string[] args)
{
MyNativeResourceWrapper obj1 = new MyNativeResourceWrapper();
obj1.Dispose();
MyNativeResourceWrapper obj2 = new MyDerivedNRW();
obj2.Dispose();
}
}
}
2. The event pattern in .net can be treated as the template method pattern also.
We know event is built on top of delegate, and delegate actually maintains a list of type safe function pointers. When we define an event, what the compiler actually does is to define a private delegate type, an add_XXX method to add object to the delegate and a remove_XXX method to remove object from the delegate. You can use tools such as reflector to check the code generated by the compiler.
Sample code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DesignPattern
{
internal class NewMailEventArgs : EventArgs
{
private string m_Subject;
private readonly string m_From;
private readonly string m_To;
public string Subject { get { return m_Subject; } }
public string From { get { return m_From; } }
public string To { get { return m_To; } }
public NewMailEventArgs(string subject, string from, string to)
{
m_Subject = subject;
m_From = from;
m_To = to;
}
}
internal class MailMgr
{
//1. define an event
public event EventHandler<NewMailEventArgs> NewMail;
//2. actual implementation method that raises the event
// which calls into the actual method that client implements
protected virtual void OnNewMail(NewMailEventArgs e)
{
EventHandler<NewMailEventArgs> temp = NewMail;
if (temp != null)
{
temp(this, e);
}
}
//3. template method to actually call implementation method
public void SimilateNewMailArrives(string subject)
{
NewMailEventArgs e = new NewMailEventArgs("hello","george","nick");
OnNewMail(e);
}
}
internal class MailClient
{
public MailClient(MailMgr mailmgr)
{
mailmgr.NewMail += HandleNewMail;
}
private void HandleNewMail(object sender, NewMailEventArgs e)
{
Console.WriteLine("get new mail with subject: {0}", e.Subject);
}
public void UnRegister(MailMgr mailmgr)
{
mailmgr.NewMail -= HandleNewMail;
}
}
class Program
{
static void Main(string[] args)
{
MailMgr mgr = new MailMgr();
MailClient client = new MailClient(mgr);
mgr.SimilateNewMailArrives("hello");
client.UnRegister(mgr);
}
}
}