細說設計模式之模板方法

1. 模板方法模式概述算法

模板方法模式定義以下:設計模式

模板方法模式:定義一個操做中算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。app

模板方法模式是一種基於繼承的代碼複用技術,它是一種類行爲型模式。框架

模板方法模式是結構最簡單的行爲型設計模式,在其結構中只存在父類與子類之間的繼承關係。經過使用模板方法模式,能夠將一些複雜流程的實現步驟封裝在一系列基本方法中,在抽象父類中提供一個稱之爲模板方法的方法來定義這些基本方法的執行次序,而經過其子類來覆蓋某些步驟,從而使得相同的算法框架能夠有不一樣的執行結果。模板方法模式提供了一個模板方法來定義算法框架,而某些具體步驟的實現能夠在其子類中完成。ide

2. 模板方法模式結構與實現函數

2.1 模式結構學習

模板方法模式結構比較簡單,其核心是抽象類和其中的模板方法的設計,其結構如圖1所示:測試

圖1 模板方法模式結構圖spa

由圖1可知,模板方法模式包含以下兩個角色:設計

(1) AbstractClass(抽象類):在抽象類中定義了一系列基本操做(PrimitiveOperations),這些基本操做能夠是具體的,也能夠是抽象的,每個基本操做對應算法的一個步驟,在其子類中能夠重定義或實現這些步驟。同時,在抽象類中實現了一個模板方法(Template Method),用於定義一個算法的框架,模板方法不只能夠調用在抽象類中實現的基本方法,也能夠調用在抽象類的子類中實現的基本方法,還能夠調用其餘對象中的方法。

(2) ConcreteClass(具體子類):它是抽象類的子類,用於實如今父類中聲明的抽象基本操做以完成子類特定算法的步驟,也能夠覆蓋在父類中已經實現的具體基本操做。

2.2 模式實現

在實現模板方法模式時,開發抽象類的軟件設計師和開發具體子類的軟件設計師之間能夠進行協做。一個設計師負責給出一個算法的輪廓和框架,另外一些設計師則負責給出這個算法的各個邏輯步驟。實現這些具體邏輯步驟的方法即爲基本方法,而將這些基本方法彙總起來的方法即爲模板方法,模板方法模式的名字也所以而來。下面將詳細介紹模板方法和基本方法:

1. 模板方法

一個模板方法是定義在抽象類中的、把基本操做方法組合在一塊兒造成一個總算法或一個總行爲的方法。這個模板方法定義在抽象類中,並由子類不加以修改地徹底繼承下來。模板方法是一個具體方法,它給出了一個頂層邏輯框架,而邏輯的組成步驟在抽象類中能夠是具體方法,也能夠是抽象方法。因爲模板方法是具體方法,所以模板方法模式中的抽象層只能是抽象類,而不是接口。

2. 基本方法

基本方法是實現算法各個步驟的方法,是模板方法的組成部分。基本方法又能夠分爲三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。

(1) 抽象方法:一個抽象方法由抽象類聲明、由其具體子類實現。在C#語言裏一個抽象方法以abstract關鍵字標識。

(2) 具體方法:一個具體方法由一個抽象類或具體類聲明並實現,其子類能夠進行覆蓋也能夠直接繼承。

(3) 鉤子方法:一個鉤子方法由一個抽象類或具體類聲明並實現,而其子類可能會加以擴展。一般在父類中給出的實現是一個空實現(可以使用virtual關鍵字將其定義爲虛函數),並以該空實現做爲方法的默認實現,固然鉤子方法也能夠提供一個非空的默認實現。

在模板方法模式中,鉤子方法有兩類:第一類鉤子方法能夠與一些具體步驟「掛鉤」,以實如今不一樣條件下執行模板方法中的不一樣步驟,這類鉤子方法的返回類型一般是bool類型的,這類方法名通常爲IsXXX(),用於對某個條件進行判斷,若是條件知足則執行某一步驟,不然將不執行,以下代碼片斷所示:

……
//模板方法
public void TemplateMethod() 
{
Open();
Display();
//經過鉤子方法來肯定某步驟是否執行
if (IsPrint()) 
{
    Print();
}
}

//鉤子方法
public bool IsPrint()
{
    return true;
}
……

在代碼中IsPrint()方法便是鉤子方法,它能夠決定Print()方法是否執行,通常狀況下,鉤子方法的返回值爲true,若是不但願某方法執行,能夠在其子類中覆蓋鉤子方法,將其返回值改成false便可,這種類型的鉤子方法能夠控制方法的執行,對一個算法進行約束。

還有一類鉤子方法就是實現體爲空的具體方法,子類能夠根據須要覆蓋或者繼承這些鉤子方法,與抽象方法相比,這類鉤子方法的好處在於子類若是沒有覆蓋父類中定義的鉤子方法,編譯能夠正常經過,可是若是沒有覆蓋父類中聲明的抽象方法,編譯將報錯。

在模板方法模式中,抽象類的典型代碼以下:

abstract class AbstractClass 
{
//模板方法
public void TemplateMethod() 
{
        PrimitiveOperation1();
        PrimitiveOperation2();
        PrimitiveOperation3();
}

//基本方法—具體方法
public void PrimitiveOperation1() 
{
    //實現代碼
}

//基本方法—抽象方法
    public abstract void PrimitiveOperation2();    

//基本方法—鉤子方法
public virtual void PrimitiveOperation3()   
{  }
}

在抽象類中,模板方法TemplateMethod()定義了算法的框架,在模板方法中調用基本方法以實現完整的算法,每個基本方法如PrimitiveOperation1()、PrimitiveOperation2()等均實現了算法的一部分,對於全部子類都相同的基本方法可在父類提供具體實現,例如PrimitiveOperation1(),不然在父類聲明爲抽象方法或鉤子方法,由不一樣的子類提供不一樣的實現,例如PrimitiveOperation2()和PrimitiveOperation3()。

可在抽象類的子類中提供抽象步驟的實現,也可覆蓋父類中已經實現的具體方法,具體子類的典型代碼以下:

class ConcreteClass : AbstractClass 
{
public override void PrimitiveOperation2() 
{
    //實現代碼
}

public override void PrimitiveOperation3() 
{
    //實現代碼
}
}

3 模板方法模式應用實例

下面經過一個應用實例來進一步學習和理解模板方法模式。

1. 實例說明

某軟件公司欲爲某銀行的業務支撐系統開發一個利息計算模塊,利息計算流程以下:

(1) 系統根據帳號和密碼驗證用戶信息,若是用戶信息錯誤,系統顯示出錯提示;

(2) 若是用戶信息正確,則根據用戶類型的不一樣使用不一樣的利息計算公式計算利息(如活期帳戶和按期帳戶具備不一樣的利息計算公式);

(3) 系統顯示利息。

試使用模板方法模式設計該利息計算模塊。

2. 實例類圖

經過分析,本實例結構圖如圖3所示。

圖3 銀行利息計算模塊結構圖

在圖3中,Account充當抽象類角色,CurrentAccount和SavingAccount充當具體子類角色。

3. 實例代碼

(1) Account:帳戶類,充當抽象類。

//Account.cs
using System;

namespace TemplateMethodSample
{
    abstract class Account
    {
        //基本方法——具體方法
        public bool Validate(string account, string password) 
        {
		    Console.WriteLine("帳號:{0}", account);
            Console.WriteLine("密碼:{0}", password);
            //模擬登陸
            if (account.Equals("張無忌") && password.Equals("123456")) 
            {
			    return true;
		    }
		    else 
            {
			    return false;
		    }
	    }

        //基本方法——抽象方法
        public abstract void CalculateInterest();

        //基本方法——具體方法
        public void Display() 
        {
            Console.WriteLine("顯示利息!");
	    }

        //模板方法
        public void Handle(string account, string password) 
        {
		    if (!Validate(account,password)) 
            {
                Console.WriteLine("帳戶或密碼錯誤!");
			    return;
		    }
		    CalculateInterest();
		    Display();
	    }
    }
}

(2) CurrentAccount:活期帳戶類,充當具體子類。

//CurrentAccount.cs
using System;

namespace TemplateMethodSample
{
    class CurrentAccount : Account
    {
        //覆蓋父類的抽象基本方法
        public override void CalculateInterest() 
        {
		    Console.WriteLine("按活期利率計算利息!");
	    }
    }
}

(3) SavingAccount:按期帳戶類,充當具體子類。

//SavingAccount.cs
using System;

namespace TemplateMethodSample
{
    class SavingAccount : Account
    {
        //覆蓋父類的抽象基本方法
        public override void CalculateInterest() 
        {
		    Console.WriteLine("按按期利率計算利息!");
	    }
    }
}

(4) 配置文件App.config,在配置文件中存儲了具體子類的類名。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="subClass" value="TemplateMethodSample.CurrentAccount"/>
  </appSettings>
</configuration>

(5) Program:客戶端測試類

//Program.cs
using System;
using System.Configuration;
using System.Reflection;

namespace TemplateMethodSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Account account;
            //讀取配置文件
            string subClassStr = ConfigurationManager.AppSettings["subClass"];
            //反射生成對象
            account = (Account)Assembly.Load("TemplateMethodSample").CreateInstance(subClassStr);
            account.Handle("張無忌", "123456");
            Console.Read();
        }
    }
}

4. 結果及分析

編譯並運行程序,輸出結果以下:

  帳號:張無忌
  密碼:123456
  按活期利率計算利息!
  顯示利息!

若是須要更換具體子類,無須修改源代碼,只需修改配置文件App.config,例如將活期帳戶(CurrentAccount)改成按期帳戶(Saving Account),只需將存儲在配置文件中的具體子類CurrentAccount改成SavingAccount,以下代碼所示:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="subClass" value="TemplateMethodSample.SavingAccount"/>
  </appSettings>
</configuration>

從新運行客戶端程序,輸出結果以下:

  帳號:張無忌
  密碼:123456
  按按期利率計算利息!
  顯示利息!

若是須要增長新的具體子類(新的帳戶類型),原有代碼均無須修改,徹底符合開閉原則。

4 鉤子方法的使用

模板方法模式中,在父類中提供了一個定義算法框架的模板方法,還提供了一系列抽象方法、具體方法和鉤子方法,其中鉤子方法的引入使得子類能夠控制父類的行爲。最簡單的鉤子方法就是空方法,代碼以下:

public virtual void Display() { }

固然也能夠在鉤子方法中定義一個默認的實現,若是子類不覆蓋鉤子方法,則執行父類的默認實現代碼。

另外一種鉤子方法能夠實現對其餘方法進行約束,這種鉤子方法一般返回一個bool類型,即返回true或false,用來判斷是否執行某一個基本方法,下面經過一個實例來講明這種鉤子方法的使用。

某軟件公司欲爲銷售管理系統提供一個數據圖表顯示功能,該功能的實現包括以下幾個步驟:

(1) 從數據源獲取數據;

(2) 將數據轉換爲XML格式;

(3) 以某種圖表方式顯示XML格式的數據。

該功能支持多種數據源和多種圖表顯示方式,但全部的圖表顯示操做都基於XML格式的數據,所以可能須要對數據進行轉換,若是從數據源獲取的數據已是XML數據則無須轉換。

因爲該數據圖表顯示功能的三個步驟次序是固定的,且存在公共代碼(例如數據格式轉換代碼),知足模板方法模式的適用條件,可使用模板方法模式對其進行設計。由於數據格式的不一樣,XML數據能夠直接顯示,而其餘格式的數據須要進行轉換,所以第(2)步「將數據轉換爲XML格式」的執行存在不肯定性,爲了解決這個問題,能夠定義一個鉤子方法IsNotXMLData()來對數據轉換方法進行控制。經過分析,該圖表顯示功能的基本結構如圖4所示:

圖4 數據圖表顯示功能結構圖

能夠將公共方法和框架代碼放在抽象父類中,代碼以下:

//DataViewer.cs
using System;

namespace TemplateMethodSample
{
    abstract class DataViewer
    {
        //抽象方法:獲取數據
        public abstract void GetData();

        //具體方法:轉換數據
        public void ConvertData() 
        {
		    Console.WriteLine("將數據轉換爲XML格式。");
	    }

        //抽象方法:顯示數據
        public abstract void DisplayData();

        //鉤子方法:判斷是否爲XML格式的數據
        public virtual bool IsNotXMLData()
        {
            return true;
        }

        //模板方法
        public void Process()
        {
            GetData();
            //若是不是XML格式的數據則進行數據轉換
            if (IsNotXMLData())
            {
                ConvertData();
            }
            DisplayData();
        }
    }
}

在上面的代碼中,引入了一個鉤子方法IsNotXMLData(),其返回類型爲bool類型,在模板方法中經過它來對數據轉換方法ConvertData()進行約束,該鉤子方法的默認返回值爲true,在子類中能夠根據實際狀況覆蓋該方法,其中用於顯示XML格式數據的具體子類XMLDataViewer代碼以下:

//XMLDataViewer.cs
using System;

namespace TemplateMethodSample
{
    class XMLDataViewer : DataViewer
    {
        //實現父類方法:獲取數據
        public override void GetData() 
        {
		    Console.WriteLine("從XML文件中獲取數據。");
	    }

        //實現父類方法:顯示數據,默認以柱狀圖方式顯示,可結合橋接模式來改進
        public override void DisplayData() 
        {
            Console.WriteLine("以柱狀圖顯示數據。");
	    }

        //覆蓋父類的鉤子方法
        public override bool IsNotXMLData()
        {
            return false;
        }
    }
}

在具體子類XMLDataViewer中覆蓋了鉤子方法IsNotXMLData(),返回false,表示該數據已爲XML格式,無須執行數據轉換方法ConvertData(),客戶端代碼以下:

//Program.cs
using System;

namespace TemplateMethodSample
{
    class Program
    {
        static void Main(string[] args)
        {
            DataViewer dv;
            dv = new XMLDataViewer();
            dv.Process();
            Console.Read();
        }
    }
}

該程序運行結果以下:

從XML文件中獲取數據。

以柱狀圖顯示數據。

5 模板方法模式效果與適用場景

模板方法模式是基於繼承的代碼複用技術,它體現了面向對象的諸多重要思想,是一種使用較爲頻繁的模式。模板方法模式普遍應用於框架設計中,以確保經過父類來控制處理流程的邏輯順序(如框架的初始化,測試流程的設置等)。

5.1 模式優勢

模板方法模式的主要優勢以下:

(1) 在父類中形式化地定義一個算法,而由它的子類來實現細節的處理,在子類實現詳細的處理算法時並不會改變算法中步驟的執行次序。

(2) 模板方法模式是一種代碼複用技術,它在類庫設計中尤其重要,它提取了類庫中的公共行爲,將公共行爲放在父類中,而經過其子類來實現不一樣的行爲,它鼓勵咱們恰當使用繼承來實現代碼複用。

(3) 可實現一種反向控制結構,經過子類覆蓋父類的鉤子方法來決定某一特定步驟是否須要執行。

(4) 在模板方法模式中能夠經過子類來覆蓋父類的基本方法,不一樣的子類能夠提供基本方法的不一樣實現,更換和增長新的子類很方便,符合單一職責原則和開閉原則。

5.2 模式缺點

模板方法模式的主要缺點以下:

須要爲每個基本方法的不一樣實現提供一個子類,若是父類中可變的基本方法太多,將會致使類的個數增長,系統更加龐大,設計也更加抽象,此時,可結合橋接模式來進行設計。

5.3 模式適用場景

在如下狀況下能夠考慮使用模板方法模式:

(1) 對一些複雜的算法進行分割,將其算法中固定不變的部分設計爲模板方法和父類具體方法,而一些能夠改變的細節由其子類來實現。即:一次性實現一個算法的不變部分,並將可變的行爲留給子類來實現。

(2) 各子類中公共的行爲應被提取出來並集中到一個公共父類中以免代碼重複。

(3) 須要經過子類來決定父類算法中某個步驟是否執行,實現子類對父類的反向控制。

相關文章
相關標籤/搜索