設計模式——模版方法模式

shanzm發表於2020-07-20
shanzm-2020年7月20日 01:55:50

1. 簡介

1.定義

模版方法模式(Template Method Pattern):用於定義一個操作中演算法的骨架,而將一些步驟延遲到子類中。

模版方法模式使得子類可以不改變一個演算法的結構及可重定義該演算法的某些特定步驟。

簡而言之:模版方法模式功能在於固定演算法骨架,而讓具體演算法實現可擴充套件。

其實只要是理解繼承和多型即可以立即明白模版方法模式。模版方法模式是通過把不變行為搬移到父類,除去子類中的重複程式碼。

2.主要類

  • AbstractClass:抽象類。用來定義演算法骨架(抽象方法)。具體的子類通過重寫抽象方法來實現一個演算法的各個步驟。

  • ConcreteClass:具體實現類。用來實現演算法骨架中的某些步驟,完成與特定子類相關的功能。

3.名詞解釋

  • 模版方法:抽象類中定義演算法骨架的方法。

  • 原語操作:即抽象方法,抽象類中定義的抽象操作。

  • 具體操作:即具體方法,抽象類定義的具體方法體的方法

  • 鉤子方法:用於決定模版方法中某個方法是否執行的方法。鉤子方法就是通過子類的行為去反向控制父類的行為的一種方法

4.抽象工廠模式的UML

Template Method Pattern UML

注:原圖片來自《設計模式實訓教程-第二版》

2. 示例

2.1 背景說明

對資料庫的操作:連線-->開啟-->使用-->關閉

對於不同的資料操作步驟都是一樣的,只是連線資料庫稍有不同

這裡我們定義一個抽象模版類DBOperator,其中我們定義ConnDB()、OpenDB()、UseDB()、CloseDB()四個抽象方法

定義不同的資料庫具體實現類:SQLServerDBOperator、OracleDBOperator

2.2 程式碼實現

①定義抽象父類

public abstract class DBOperator
{
    //原語操作1
    public abstract void ConnDB();
    //原語操作2
    public void OpenDB()
    {
        Console.WriteLine("開啟資料庫");
    }
    //原語操作3
    public void UseDB()
    {
        Console.WriteLine("使用資料庫");
    }
    //原語操作4
    public void CloseDB()
    {
        Console.WriteLine("關閉資料庫");
    }

    //鉤子方法:用於子類反向控制父類中某個方法是否執行
    //注意鉤子方法這裡定義為虛方法,虛方法和抽象方法不同的地方就是虛方法有方法體,這樣我們就可以在虛方法中定義默的方法
    public virtual bool IsStart()
    {
        return true;//預設為true所以在具體子類中重寫
    }

    //模版方法:定義演算法骨架
    //確定其他基本方法的執行順序
    public void Process()
    {
        ConnDB();
        OpenDB();
        if (IsStart())//通過鉤子方法控制是否執行UseDB()
        {
            UseDB();
        }
        CloseDB();
    }
}

②定義具體實現類

 //具體子類1
public class OracleDBOperator : DBOperator
{
    public override void ConnDB()
    {
        Console.WriteLine("連線Oracle資料庫");
    }
    //按照物件導向的思想,我們可以在子類中使用new 關鍵字覆蓋父類中的方法
    //注意:
    //1. 這裡覆蓋了UseDB(),是不夠的,還要覆蓋呼叫這個方法的方法Process()
    //2. 若是父類引用指向子類物件,則需要將父類轉化為子類物件才可以使用子類中覆蓋的方法
    public new void UseDB()
    {
        Console.WriteLine("使用Oracle資料庫");
    }

    public new void Process()
    {
        ConnDB();
        OpenDB();
        if (IsStart())
        {
            UseDB();
        }
        CloseDB();
    }
}

//具體子類2
public class SQLServerDBOperator : DBOperator
{
    ///具體子類繼承抽象父類,重寫抽象父類中的抽象方法
    ///抽象父類中的非抽象方法就是每個子類都通用的方法,
    public override void ConnDB()
    {
        Console.WriteLine("連線SQL Server資料庫");
    }

    public override bool IsStart()//覆蓋了鉤子函式,修改了抽象父類中模版方法,實現子類反向控制父類
    {
        return false;
    }
}

③客戶端

static void Main(string[] args)
{
     static void Main(string[] args)
        {
            DBOperator sqlServerDBOperator = new SQLServerDBOperator();
            DBOperator oracleDBOperator = new OracleDBOperator();

            sqlServerDBOperator.Process(); //注意這裡,我們定義的鉤子方法是虛方法,通過重寫,實現子類控制父類,這裡是不需要將強轉為子類物件
            Console.WriteLine("---------------------------");

            oracleDBOperator.Process();
            Console.WriteLine("---------------------------");

            ((OracleDBOperator)oracleDBOperator).Process(); //這裡是通過覆蓋父類中的方法,所以需要將父類強轉為相應的子類物件
            Console.WriteLine("---------------------------");

            Console.ReadKey();
        }
}

④執行結果

連線SQL Server資料庫
開啟資料庫
關閉資料庫
---------------------------
連線Oracle資料庫
開啟資料庫
使用資料庫
關閉資料庫
---------------------------
連線Oracle資料庫
開啟資料庫
使用Oracle資料庫
關閉資料庫


3. 總結分析

3.1 補充

1.模版方法模式中為何使用的是抽象類而不是介面?

這裡先說明一下介面和抽象類的區別:

  • 介面只是定義一組行為,某個類(非抽象類)實現這個介面就需要使用該介面中的所有方法。簡而言之:介面約束實現該介面的類的行為。所以介面也稱之為契約。

  • 抽象類中的抽象方法需要子類重寫,而抽象類可以有非抽象方法,非抽象方法是需要在抽象類中寫方法體的。簡而言之:抽象類不僅約束子類的行為而且需要為子類中非抽象方法提供方法體

這裡我們使用抽象類而不是使用介面,為什麼呢?若是定義為抽象方法則我們需要在實現類中需要對抽象方法都重寫,而可能多個子類中的某個方法實現是一樣的。

這裡也體現了介面和抽象類的不同之處:介面約束實現介面的類行為(所以介面也稱之為契約)而抽象類不僅要約束子類的行為,而要為子類提供公共方法體

這裡示例中的資料庫連線函式ConnDB定義為抽象方法,因為每個資料庫的連線方法是不一樣的,所以在每個具體的實現類中都需要對其重寫 。而其他的資料庫操作方法類似,所以我們在父類中對其實現,避免子類中程式碼冗餘。

3.2 優點

模板方法模式的優點是實現程式碼複用。
模板方法模式是一種實現程式碼複用的很好的手段。通過把子類的公共功能提煉和抽取,把公共部分放到模板中去實現。

模版方法模式體現類開閉原則。首先模版方法模式從設計上分離變與不變,然後把不變的部分抽取出來,定義到父類中,比如演算法骨架,一些公共的、固定的實現等。這些不變的部分被封閉起來,儘量不去修改它們。要想擴充套件新的功能,那就使用子類來擴充套件,通過子類來實現可變化的步驟,對於這種新增功能的做法是開放的。

3.3 缺點

模板方法模式的缺點是演算法骨架不容易升級。
模板方法模式最基本的功能就是通過模板的制定,把演算法骨架完全固定下來。事實上模板和子類是非常耦合的,如果要對模板中的演算法骨架進行變更,可能就會要求所有相關的子類進行相應的變化。比如模版方法中新增一個抽象方法,則所有的子類都需要去實現這方法。
所以抽取演算法骨架的時候要特別小心,儘量確保是不會變化的部分才放到模板中。

3.4 適應場合

建議在以下情況中選用模板方法模式

  • 需要固定定義演算法骨架。實現一個演算法的不變的部分,並把可變的行為留給子類來實現的情況。
  • 各個子類中具有公共行為,應該抽取出來,集中在一個公共類中去實現,從而避免程式碼重複。
  • 需要控制子類擴充套件的情況。模板方法模式會在特定的點來呼叫子類的方法,這樣只允許在這些點進行擴充套件。

3.5 對比與合用

【TODO】(待策略模式研究完在完成此部分!)

  • 模版方法模式與工廠方法模式

  • 模版方法模式和策略模式


4. 參考及原始碼

相關文章