主要內容
1.概述
2.Template Method解說
3..NET中的Template Method模式
4.適用性及實現要點
意圖
定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。[-GOF《設計模式》]
結構圖
圖1 Template Method 模式結構圖
生活中的例子
模板方法定義了一個操作中算法的骨架,而將一些步驟延遲到子類中。房屋建築師在開發新項目時會使用模板方法。一個典型的規劃包括一些建築平面圖,每個平面圖體現了不同部分。在一個平面圖中,地基、結構、上下水和走線對於每個房間都是一樣的。只有在建築的後期才開始有差別而產生了不同的房屋樣式。
圖2 使用建築圖為例子的Template Method模式
Template Method模式解說
李建忠老師說過一句話,如果你只想掌握一種設計模式的話,那這個模式一定是Template Method模式。對於這個問題,我想可能是仁者見仁,智者見智,但是有一點不能否認的Template Method模式是非常簡單而且幾乎是無處不用,很少有人沒有用過它。下面我們以一個簡單的數據庫查詢的例子來說明Template Method模式(注意:這個例子在實際數據庫開發中並沒有任何實際意義,這裡僅僅是為了作為示例而已)。
假如我們需要簡單的讀取Northwind數據庫中的表的記錄並顯示出來。對於數據庫操作,我們知道不管讀取的是哪張表,它一般都應該經過如下這樣的幾步:
1.連接數據庫(Connect)
2.執行查詢命令(Select)
3.顯示數據(Display)
4.斷開數據庫連接(Disconnect)
這些步驟是固定的,但是對於每一張具體的數據表所執行的查詢卻是不一樣的。顯然這需要一個抽象角色,給出頂級行為的實現。如下圖:
圖3
Template Method模式的實現方法是從上到下,我們首先給出頂級框架DataAccessObject的實現邏輯:
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();
}
}
顯然在這個頂級的框架DataAccessObject中給出了固定的輪廓,方法Run()便是模版方法,Template Method模式也由此而得名。而對於Select()和Display()這兩個抽象方法則留給具體的子類去實現,如下圖:
圖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模式的一個特征就是剝離共同點。
.NET 中的Template Method模式
.NET Framework中Template Method模式的使用可以說是無處不在,比如說我們需要自定義一個文本控件,會讓它繼承於RichTextBox,並重寫其中部分事件,如下例所示:
public class MyRichTextBox : RichTextBox
{
private static bool m_bPaint = true;
private string m_strLine = "";
private int m_nContentLength = 0;
private int m_nLineLength = 0;
private int m_nLineStart = 0;
private int m_nLineEnd = 0;
private string m_strKeywords = "";
private int m_nCurSelection = 0;
protected override void OnSelectionChanged(EventArgs e)
{
m_nContentLength = this.TextLength;
int nCurrentSelectionStart = SelectionStart;
int nCurrentSelectionLength = SelectionLength;
m_bPaint = false;
m_nLineStart = nCurrentSelectionStart;
while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != ',')&& (Text[m_nLineStart - 1] != '{')&& (Text[m_nLineStart - 1] != '('))
m_nLineStart--;
m_nLineEnd = nCurrentSelectionStart;
while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != ',')&& (Text[m_nLineEnd] != '}')&& (Text[m_nLineEnd] != ')')&& (Text[m_nLineEnd] != '{'))
m_nLineEnd++;
m_nLineLength = m_nLineEnd - m_nLineStart;
m_strLine = Text.Substring(m_nLineStart, m_nLineLength);
this.SelectionStart = m_nLineStart;
this.SelectionLength = m_nLineLength;
m_bPaint = true;
}
protected override void OnTextChanged(EventArgs e)
{
// 重寫OnTextChanged
}
}
其中OnSelectionChanged()和OnTextChanged()便是Template Method模式中的基本方法之一,也就是子步驟方法,它們的調用已經在RichTextBox中實現了。
實現要點
1.Template Method模式是一種非常基礎性的設計模式,在面向對象系統中有著大量的應用。它用最簡潔的機制(虛函數的多態性)為很多應用程序框架提供了靈活的擴展點,是代碼復用方面的基本實現結構。
2.除了可以靈活應對子步驟的變化外,“不用調用我,讓我來調用你”的反向控制結構是Template Method的典型應用。
3.在具體實現方面,被Template Method調用的虛方法可以具有實現,也可以沒有任何實現(抽象方法,純虛方法),但一般推薦將它們設置為protected方法。[李建忠]
適用性
1.一次性實現一個算法的不變的部分,並將可變的行為留給子類來實現。
2.各子類中公共的行為應被提取出來並集中到一個公共父類中以避免代碼重復。這是Opdyke和Johnson所描述過的“重分解以一般化”的一個很好的例子。首先識別現有代碼中的不同之處,並且將不同之處分離為新的操作。最後,用一個調用這些新的操作的模板方法來替換這些不同的代碼。
3.控制子類擴展。模板方法只在特定點調用“Hook”操作,這樣就只允許在這些點進行擴展。
總結
Template Method模式是非常簡單的一種設計模式,但它卻是代碼復用的一項基本的技術,在類庫中尤其重要。