Template Method模式解说
李建忠老师说过一句话,如果你只想掌握一种设计模式的话,那这个模式一定是Template Method模式。对于这个问题,我想可能是仁者见仁,智者见智,但是有一点不能否认的Template Method模式是非常简单而且几乎是无处不用,很少有人没有用过它。下面我们以一个简单的数据库查询的例子来说明Template Method模式(注意:这个例子在实际数据库开发中并没有任何实际意义,这里仅仅是为了作为示例而已)。
假如我们需要简单的读取Northwind数据库中的表的记录并显示出来。对于数据库操作,我们知道不管读取的是哪张表,它一般都应该经过如下这样的几步:
1.连接数据库(Connect)
2.执行查询命令(Select)
3.显示数据(Display)
4.断开数据库连接(Disconnect)
这些步骤是固定的,但是对于每一张具体的数据表所执行的查询却是不一样的。显然这需要一个抽象角色,给出优异行为的实现。如下图:

图3
Template Method模式的实现方法是从上到下,我们首先给出优异框架DataAccessObject的实现逻辑:
显然在这个优异的框架DataAccessObject中给出了固定的轮廓,方法Run()便是模版方法,Template Method模式也由此而得名。而对于Select()和Display()这两个抽象方法则留给具体的子类去实现,如下图:public abstract class DataAccessObject
![]()
{
protected string connectionString;
![]()
protected DataSet dataSet;
![]()
public virtual void Connect()
![]()
{
connectionString =
![]()
"Server=Rj-097;User Id=sa;Password=sa;Database=Northwind";
![]()
}
![]()
public abstract void Select();
![]()
public abstract void Display();
![]()
![]()
public virtual void Disconnect()
![]()
{
connectionString = "";
}
![]()
// The "Template Method"
![]()
public void Run()
![]()
{
Connect();
![]()
Select();
![]()
Display();
![]()
Disconnect();
}
}

图4
示意性实现代码:
class Categories : DataAccessObject
![]()
{
public override void Select()
{
string sql = "select CategoryName from Categories";
![]()
SqlDataAdapter dataAdapter = new SqlDataAdapter(
![]()
sql, connectionString);
![]()
dataSet = new DataSet();
![]()
dataAdapter.Fill(dataSet, "Categories");
![]()
}
![]()
public override void Display()
![]()
{
![]()
Console.WriteLine("Categories ---- ");
![]()
DataTable dataTable = dataSet.Tables["Categories"];
![]()
foreach (DataRow row in dataTable.Rows)
![]()
{
![]()
Console.WriteLine(row["CategoryName"].ToString());
![]()
}
![]()
Console.WriteLine();
![]()
}
}
再来看看客户端程序的调用,不需要再去调用每一个步骤的方法:class Products : DataAccessObject
![]()
{
public override void Select()
![]()
{
string sql = "select top 10 ProductName from Products";
![]()
SqlDataAdapter dataAdapter = new SqlDataAdapter(
![]()
sql, connectionString);
![]()
dataSet = new DataSet();
![]()
dataAdapter.Fill(dataSet, "Products");
![]()
}
![]()
public override void Display()
![]()
{
![]()
Console.WriteLine("Products ---- ");
![]()
DataTable dataTable = dataSet.Tables["Products"];
![]()
foreach (DataRow row in dataTable.Rows)
![]()
{
Console.WriteLine(row["ProductName"].ToString());
![]()
}
![]()
Console.WriteLine();
![]()
}
![]()
}
在上面的例子中,需要注意的是:public class App
![]()
{
static void Main()
{
![]()
DataAccessObject dao;
![]()
![]()
dao = new Categories();
![]()
dao.Run();
![]()
![]()
dao = new Products();
![]()
dao.Run();
![]()
// Wait for user
![]()
Console.Read();
![]()
}
![]()
}
1.对于Connect()和Disconnect()方法实现为了virtual,而Select()和Display()方法则为abstract,这是因为如果这个方法有默认的实现,则实现为virtual,否则为abstract。
2.Run()方法作为一个模版方法,它的一个重要特征是:在基类里定义,而且不能够被派生类更改。有时候它是私有方法(private method),但实际上它经常被声明为protected。它通过调用其它的基类方法(覆写过的)来工作,但它经常是作为初始化过程的一部分被调用的,这样就没必要让客户端程序员能够直接调用它了。
3.在一开始我们提到了不管读的是哪张数据表,它们都有共同的操作步骤,即共同点。因此可以说Template Method模式的一个特征就是剥离共同点。