天天看點

.NET設計模式-模版方法(Template Method)

摘要:Template Method模式是比較簡單的設計模式之一,但它卻是代碼複用的一項基本的技術,在類庫中尤其重要。

主要内容

1.概述

2.Template Method解說

3..NET中的Template Method模式

4.适用性及實作要點

概述

變化一直以來都是軟體設計的永恒話題,在XP程式設計中提倡擁抱變化,積極應對。如何更好的去抓住變化點,應對變化?如何更好的提高代碼複用?通過學習Template Method模式,您應該有一個新的認識。

意圖

定義一個操作中的算法的骨架,而将一些步驟延遲到子類中。Template Method使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。[-GOF《設計模式》]

結構圖

.NET設計模式-模版方法(Template Method)

圖1 Template Method 模式結構圖

生活中的例子

模闆方法定義了一個操作中算法的骨架,而将一些步驟延遲到子類中。房屋建築師在開發新項目時會使用模闆方法。一個典型的規劃包括一些建築平面圖,每個平面圖展現了不同部分。在一個平面圖中,地基、結構、上下水和走線對于每個房間都是一樣的。隻有在建築的後期才開始有差别而産生了不同的房屋樣式。

.NET設計模式-模版方法(Template Method)

圖2 使用建築圖為例子的Template Method模式

Template Method模式解說

李建忠老師說過一句話,如果你隻想掌握一種設計模式的話,那這個模式一定是Template Method模式。對于這個問題,我想可能是仁者見仁,智者見智,但是有一點不能否認的Template Method模式是非常簡單而且幾乎是無處不用,很少有人沒有用過它。下面我們以一個簡單的資料庫查詢的例子來說明Template Method模式(注意:這個例子在實際資料庫開發中并沒有任何實際意義,這裡僅僅是為了作為示例而已)。

假如我們需要簡單的讀取Northwind資料庫中的表的記錄并顯示出來。對于資料庫操作,我們知道不管讀取的是哪張表,它一般都應該經過如下這樣的幾步:

1.連接配接資料庫(Connect)

2.執行查詢指令(Select)

3.顯示資料(Display)

4.斷開資料庫連接配接(Disconnect)

這些步驟是固定的,但是對于每一張具體的資料表所執行的查詢卻是不一樣的。顯然這需要一個抽象角色,給出頂級行為的實作。如下圖:

.NET設計模式-模版方法(Template Method)

圖3

Template Method模式的實作方法是從上到下,我們首先給出頂級架構DataAccessObject的實作邏輯:

.NET設計模式-模版方法(Template Method)

public abstract class DataAccessObject

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

{

.NET設計模式-模版方法(Template Method)

    protected string connectionString;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    protected DataSet dataSet;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public virtual void Connect()

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    { 

.NET設計模式-模版方法(Template Method)

        connectionString = 

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

            "Server=Rj-097;User Id=sa;Password=sa;Database=Northwind";

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    }

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public abstract void Select();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public abstract void Display();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public virtual void Disconnect()

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    {

.NET設計模式-模版方法(Template Method)

        connectionString = "";

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    // The "Template Method" 

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public void Run()

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Connect();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Select();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Display();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Disconnect();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

}

顯然在這個頂級的架構DataAccessObject中給出了固定的輪廓,方法Run()便是模版方法,Template Method模式也由此而得名。而對于Select()和Display()這兩個抽象方法則留給具體的子類去實作,如下圖:

.NET設計模式-模版方法(Template Method)

圖4

示意性實作代碼:

.NET設計模式-模版方法(Template Method)

class Categories : DataAccessObject

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public override void Select()

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        string sql = "select CategoryName from Categories";

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        SqlDataAdapter dataAdapter = new SqlDataAdapter(

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

            sql, connectionString);

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        dataSet = new DataSet();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        dataAdapter.Fill(dataSet, "Categories");

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    public override void Display()

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Console.WriteLine("Categories ---- ");

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        DataTable dataTable = dataSet.Tables["Categories"];

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        foreach (DataRow row in dataTable.Rows)

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        {

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

            Console.WriteLine(row["CategoryName"].ToString());

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        }

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Console.WriteLine();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

class Products : DataAccessObject

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        string sql = "select top 10 ProductName from Products";

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        dataAdapter.Fill(dataSet, "Products");

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Console.WriteLine("Products ---- ");

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        DataTable dataTable = dataSet.Tables["Products"];

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

            Console.WriteLine(row["ProductName"].ToString());

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

再來看看用戶端程式的調用,不需要再去調用每一個步驟的方法:

.NET設計模式-模版方法(Template Method)

public class App

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    static void Main()

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        DataAccessObject dao;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        dao = new Categories();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        dao.Run();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        dao = new Products();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        // Wait for user 

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        Console.Read();

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

在上面的例子中,需要注意的是:

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,并重寫其中部分事件,如下例所示:

.NET設計模式-模版方法(Template Method)

public class MyRichTextBox : RichTextBox

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private static bool m_bPaint = true;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private string m_strLine = "";

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private int m_nContentLength = 0;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private int m_nLineLength = 0;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private int m_nLineStart = 0;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private int m_nLineEnd = 0;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private string m_strKeywords = "";

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    private int m_nCurSelection = 0;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    protected override void OnSelectionChanged(EventArgs e)

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_nContentLength = this.TextLength;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        int nCurrentSelectionStart = SelectionStart;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        int nCurrentSelectionLength = SelectionLength;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_bPaint = false;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_nLineStart = nCurrentSelectionStart;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != ',')&& (Text[m_nLineStart - 1] != '{')&& (Text[m_nLineStart - 1] != '('))

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

            m_nLineStart--;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_nLineEnd = nCurrentSelectionStart;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != ',')&& (Text[m_nLineEnd] != '}')&& (Text[m_nLineEnd] != ')')&& (Text[m_nLineEnd] != '{'))

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

            m_nLineEnd++;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_nLineLength = m_nLineEnd - m_nLineStart;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_strLine = Text.Substring(m_nLineStart, m_nLineLength);

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        this.SelectionStart = m_nLineStart;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        this.SelectionLength = m_nLineLength;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        m_bPaint = true;

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

    protected override void OnTextChanged(EventArgs e)

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

        // 重寫OnTextChanged

.NET設計模式-模版方法(Template Method)
.NET設計模式-模版方法(Template Method)

其中OnSelectionChanged()和OnTextChanged()便是Template Method模式中的基本方法之一,也就是子步驟方法,它們的調用已經在RichTextBox中實作了。

實作要點

1.Template Method模式是一種非常基礎性的設計模式,在面向對象系統中有着大量的應用。它用最簡潔的機制(虛函數的多态性)為很多應用程式架構提供了靈活的擴充點,是代碼複用方面的基本實作結構。

2.除了可以靈活應對子步驟的變化外,“不用調用我,讓我來調用你”的反向控制結構是Template Method的典型應用。

3.在具體實作方面,被Template Method調用的虛方法可以具有實作,也可以沒有任何實作(抽象方法,純虛方法),但一般推薦将它們設定為protected方法。[李建忠]

适用性

1.一次性實作一個算法的不變的部分,并将可變的行為留給子類來實作。

2.各子類中公共的行為應被提取出來并集中到一個公共父類中以避免代碼重複。這是Opdyke和Johnson所描述過的“重分解以一般化”的一個很好的例子。首先識别現有代碼中的不同之處,并且将不同之處分離為新的操作。最後,用一個調用這些新的操作的模闆方法來替換這些不同的代碼。

3.控制子類擴充。模闆方法隻在特定點調用“Hook”操作,這樣就隻允許在這些點進行擴充。

總結

Template Method模式是非常簡單的一種設計模式,但它卻是代碼複用的一項基本的技術,在類庫中尤其重要。

本篇文章寫的比較簡單,請大家見諒。更多的設計模式文章可以通路《.NET設計模式系列文章》

參考資料

Erich Gamma等,《設計模式:可複用面向對象軟體的基礎》,機械工業出版社

Robert C.Martin,《靈活軟體開發:原則、模式與實踐》,清華大學出版社

閻宏,《Java與模式》,電子工業出版社

Alan Shalloway James R. Trott,《Design Patterns Explained》,中國電力出版社

MSDN WebCast 《C#面向對象設計模式縱橫談(14):Template Method模版方法模式(結構型模式)》

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。