.NET設計模式-模版方法(Template Method)
阿新 • • 發佈:2019-02-03
摘要:Template Method模式是比較簡單的設計模式之一,但它卻是程式碼複用的一項基本的技術,在類庫中尤其重要。
主要內容
1.概述
2.Template Method解說
3..NET中的Template Method模式
4.適用性及實現要點
概述
變化一直以來都是軟體設計的永恆話題,在XP程式設計中提倡擁抱變化,積極應對。如何更好的去抓住變化點,應對變化?如何更好的提高程式碼複用?通過學習Template Method模式,您應該有一個新的認識。
意圖
定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。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模式是非常簡單的一種設計模式,但它卻是程式碼複用的一項基本的技術,在類庫中尤其重要。
本篇文章寫的比較簡單,請大家見諒。更多的設計模式文章可以訪問《.NET設計模式系列文章》
主要內容
1.概述
2.Template Method解說
3..NET中的Template Method模式
4.適用性及實現要點
概述
變化一直以來都是軟體設計的永恆話題,在XP程式設計中提倡擁抱變化,積極應對。如何更好的去抓住變化點,應對變化?如何更好的提高程式碼複用?通過學習Template Method模式,您應該有一個新的認識。
意圖
定義一個操作中的演算法的骨架,而將一些步驟延遲到子類中。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模式是非常簡單的一種設計模式,但它卻是程式碼複用的一項基本的技術,在類庫中尤其重要。
本篇文章寫的比較簡單,請大家見諒。更多的設計模式文章可以訪問《.NET設計模式系列文章》