模板方法模式(模板方法設計模式)詳解

參考地址

AQS定義的抽象方法,在子類實現有用到模版方法設計模式.

前言

在面向對象程序設計過程中,程序員常常會遇到這種情況:設計一個系統時知道了算法所需的關鍵步驟,而且確定了這些步驟的執行順序,但某些步驟的具體實現還未知,或者說某些步驟的實現與具體的環境相關。

例如,去銀行辦理業務一般要經過以下4個流程:取號、排隊、辦理具體業務、對銀行工作人員進行評分等,其中取號、排隊和對銀行工作人員進行評分的業務對每個客戶是一樣的,可以在父類中實現,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以延遲到子類中實現。

這樣的例子在生活中還有很多,例如,一個人每天會起牀、喫飯、做事、睡覺等,其中“做事”的內容每天可能不同。我們把這些規定了流程或格式的實例定義成模板,允許使用者根據自己的需求去更新它,例如,簡歷模板、論文模板、Word 中模板文件等。

以下介紹的模板方法模式將解決以上類似的問題。

模式的定義與特點

模板方法(Template Method)模式的定義如下:定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行爲型模式。

該模式的主要優點如下。

  1. 它封裝了不變部分,擴展可變部分。它把認爲是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
  2. 它在父類中提取了公共的部分代碼,便於代碼複用。
  3. 部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。

該模式的主要缺點如下。

  1. 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
  2. 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。

模式的結構與實現

模板方法模式需要注意抽象類與具體子類之間的協作。它用到了虛函數的多態性技術以及“不用調用我,讓我來調用你”的反向控制技術。現在來介紹它們的基本結構

1. 模式的結構

(1) 抽象類(Abstract Class):負責給出一個算法的輪廓和骨架。它由一個模板方法和若干個基本方法構成。這些方法的定義如下。

① 模板方法:定義了算法的骨架,按某種順序調用其包含的基本方法。

② 基本方法:是整個算法中的一個步驟,包含以下幾種類型。

  • 抽象方法:在抽象類中申明,由具體子類實現。
  • 具體方法:在抽象類中已經實現,在具體子類中可以繼承或重寫它。
  • 鉤子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。

(2) 具體子類(Concrete Class):實現抽象類中所定義的抽象方法和鉤子方法,它們是一個頂級邏輯的一個組成步驟。

模板方法模式的結構圖如圖 1 所示。

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

2. 模式的實現

模板方法模式的代碼如下:

package templateMethod;
public class TemplateMethodPattern
{
    public static void main(String[] args)
    {
        AbstractClass tm=new ConcreteClass();
        tm.TemplateMethod();
    }
}
//抽象類
abstract class AbstractClass
{
    public void TemplateMethod() //模板方法
    {
        SpecificMethod();
        abstractMethod1();          
         abstractMethod2();
    }  
    public void SpecificMethod() //具體方法
    {
        System.out.println("抽象類中的具體方法被調用...");
    }   
    public abstract void abstractMethod1(); //抽象方法1
    public abstract void abstractMethod2(); //抽象方法2
}
//具體子類
class ConcreteClass extends AbstractClass
{
    public void abstractMethod1()
    {
        System.out.println("抽象方法1的實現被調用...");
    }   
    public void abstractMethod2()
    {
        System.out.println("抽象方法2的實現被調用...");
    }
}

程序的運行結果如下:

抽象類中的具體方法被調用...
抽象方法1的實現被調用...
抽象方法2的實現被調用...

模式的應用實例

【例1】用模板方法模式實現出國留學手續設計程序。

分析:出國留學手續一般經過以下流程:索取學校資料,提出入學申請,辦理因私出國護照、出境卡和公證,申請簽證,體檢、訂機票、準備行裝,抵達目標學校等,其中有些業務對各個學校是一樣的,但有些業務因學校不同而不同,所以比較適合用模板方法模式來實現。

在本實例中,我們先定義一個出國留學的抽象類 StudyAbroad,裏面包含了一個模板方法 TemplateMethod(),該方法中包含了辦理出國留學手續流程中的各個基本方法,其中有些方法的處理由於各國都一樣,所以在抽象類中就可以實現,但有些方法的處理各國是不同的,必須在其具體子類(如美國留學類 StudyInAmerica)中實現。如果再增加一個國家,只要增加一個子類就可以了,圖 2 所示是其結構圖。

圖2 出國留學手續設計程序的結構圖

程序代碼如下:

package templateMethod;
public class StudyAbroadProcess
{
    public static void main(String[] args)
    {
        StudyAbroad tm=new StudyInAmerica();
        tm.TemplateMethod();
    }
}
//抽象類: 出國留學
abstract class StudyAbroad
{
    public void TemplateMethod() //模板方法
    {
        LookingForSchool(); //索取學校資料
        ApplyForEnrol();    //入學申請      
        ApplyForPassport(); //辦理因私出國護照、出境卡和公證
        ApplyForVisa();     //申請簽證
        ReadyGoAbroad();    //體檢、訂機票、準備行裝
        Arriving();         //抵達
    }              
    public void ApplyForPassport()
    {
        System.out.println("三.辦理因私出國護照、出境卡和公證:");
        System.out.println("  1)持錄取通知書、本人戶口簿或身份證向戶口所在地公安機關申請辦理因私出國護照和出境卡。");
        System.out.println("  2)辦理出生公證書,學歷、學位和成績公證,經歷證書,親屬關係公證,經濟擔保公證。");
    }
    public void ApplyForVisa()
    {
        System.out.println("四.申請簽證:");
        System.out.println("  1)準備申請國外境簽證所需的各種資料,包括個人學歷、成績單、工作經歷的證明;個人及家庭收入、資金和財產證明;家庭成員的關係證明等;");
        System.out.println("  2)向擬留學國家駐華使(領)館申請入境簽證。申請時需按要求填寫有關表格,遞交必需的證明材料,繳納簽證。有的國家(比如美國、英國、加拿大等)在申請簽證時會要求申請人前往使(領)館進行面試。");
    }
    public void ReadyGoAbroad()
    {
        System.out.println("五.體檢、訂機票、準備行裝:");
        System.out.println("  1)進行身體檢查、免疫檢查和接種傳染病疫苗;");
        System.out.println("  2)確定機票時間、航班和轉機地點。");
    }
    public abstract void LookingForSchool();//索取學校資料
    public abstract void ApplyForEnrol();   //入學申請
    public abstract void Arriving();        //抵達
}
//具體子類: 美國留學
class StudyInAmerica extends StudyAbroad
{
    @Override
    public void LookingForSchool()
    {
        System.out.println("一.索取學校以下資料:");
        System.out.println("  1)對留學意向國家的政治、經濟、文化背景和教育體制、學術水平進行較爲全面的瞭解;");
        System.out.println("  2)全面瞭解和掌握國外學校的情況,包括歷史、學費、學制、專業、師資配備、教學設施、學術地位、學生人數等;");
        System.out.println("  3)瞭解該學校的住宿、交通、醫療保險情況如何;");
        System.out.println("  4)該學校在中國是否有授權代理招生的留學中介公司?");
        System.out.println("  5)掌握留學簽證情況;");
        System.out.println("  6)該國政府是否允許留學生合法打工?");
        System.out.println("  8)畢業之後可否移民?");
        System.out.println("  9)文憑是否受到我國認可?");
    }
    @Override
    public void ApplyForEnrol()
    {
        System.out.println("二.入學申請:");
        System.out.println("  1)填寫報名表;");
        System.out.println("  2)將報名表、個人學歷證明、最近的學習成績單、推薦信、個人簡歷、託福或雅思語言考試成績單等資料寄往所申請的學校;");
        System.out.println("  3)爲了給簽證辦理留有充裕的時間,建議越早申請越好,一般提前1年就比較從容。");       
    }
    @Override
    public void Arriving()
    {
        System.out.println("六.抵達目標學校:");
        System.out.println("  1)安排住宿;");
        System.out.println("  2)瞭解校園及周邊環境。");
    }
}

程序的運行結果如下:

一.索取學校以下資料:
  1)對留學意向國家的政治、經濟、文化背景和教育體制、學術水平進行較爲全面的瞭解;
  2)全面瞭解和掌握國外學校的情況,包括歷史、學費、學制、專業、師資配備、教學設施、學術地位、學生人數等;
  3)瞭解該學校的住宿、交通、醫療保險情況如何;
  4)該學校在中國是否有授權代理招生的留學中介公司?
  5)掌握留學簽證情況;
  6)該國政府是否允許留學生合法打工?
  8)畢業之後可否移民?
  9)文憑是否受到我國認可?
二.入學申請:
  1)填寫報名表;
  2)將報名表、個人學歷證明、最近的學習成績單、推薦信、個人簡歷、託福或雅思語言考試成績單等資料寄往所申請的學校;
  3)爲了給簽證辦理留有充裕的時間,建議越早申請越好,一般提前1年就比較從容。
三.辦理因私出國護照、出境卡和公證:
  1)持錄取通知書、本人戶口簿或身份證向戶口所在地公安機關申請辦理因私出國護照和出境卡。
  2)辦理出生公證書,學歷、學位和成績公證,經歷證書,親屬關係公證,經濟擔保公證。
四.申請簽證:
  1)準備申請國外境簽證所需的各種資料,包括個人學歷、成績單、工作經歷的證明;個人及家庭收入、資金和財產證明;家庭成員的關係證明等;
  2)向擬留學國家駐華使(領)館申請入境簽證。申請時需按要求填寫有關表格,遞交必需的證明材料,繳納簽證。有的國家(比如美國、英國、加拿大等)在申請簽證時會要求申請人前往使(領)館進行面試。
五.體檢、訂機票、準備行裝:
  1)進行身體檢查、免疫檢查和接種傳染病疫苗;
  2)確定機票時間、航班和轉機地點。
六.抵達目標學校:
  1)安排住宿;
  2)瞭解校園及周邊環境。

模式的應用場景

模板方法模式通常適用於以下場景。

  1. 算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現。
  2. 當多個子類存在公共的行爲時,可以將其提取出來並集中到一個公共父類中以避免代碼重複。首先,要識別現有代碼中的不同之處,並且將不同之處分離爲新的操作。最後,用一個調用這些新的操作的模板方法來替換這些不同的代碼。
  3. 當需要控制子類的擴展時,模板方法只在特定點調用鉤子操作,這樣就只允許在這些點進行擴展。

模式的擴展

在模板方法模式中,基本方法包含:抽象方法、具體方法和鉤子方法,正確使用“鉤子方法”可以使得子類控制父類的行爲。如下面例子中,可以通過在具體子類中重寫鉤子方法 HookMethod1() 和 HookMethod2() 來改變抽象父類中的運行結果,其結構圖如圖 3 所示。

圖3 含鉤子方法的模板方法模式的結構圖

程序代碼如下:

package templateMethod;
public class HookTemplateMethod
{
    public static void main(String[] args)
    {
        HookAbstractClass tm=new HookConcreteClass();
        tm.TemplateMethod();
    }
}
//含鉤子方法的抽象類
abstract class HookAbstractClass
{
    public void TemplateMethod() //模板方法
    {
        abstractMethod1();
        HookMethod1();
        if(HookMethod2())
        {
            SpecificMethod();   
        }
         abstractMethod2();
    }  
    public void SpecificMethod() //具體方法
    {
        System.out.println("抽象類中的具體方法被調用...");
    }
    public void HookMethod1(){}  //鉤子方法1
    public boolean HookMethod2() //鉤子方法2
    {
        return true;
    }
    public abstract void abstractMethod1(); //抽象方法1
    public abstract void abstractMethod2(); //抽象方法2
}
//含鉤子方法的具體子類
class HookConcreteClass extends HookAbstractClass
{
    public void abstractMethod1()
    {
        System.out.println("抽象方法1的實現被調用...");
    }   
    public void abstractMethod2()
    {
        System.out.println("抽象方法2的實現被調用...");
    }   
    public void HookMethod1()
    {
        System.out.println("鉤子方法1被重寫...");
    }
    public boolean HookMethod2()
    {
        return false;
    }
}

程序的運行結果如下:

抽象方法1的實現被調用...
鉤子方法1被重寫...
抽象方法2的實現被調用...

如果鉤子方法 HookMethod1() 和鉤子方法 HookMethod2() 的代碼改變,則程序的運行結果也會改變。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章