1.前言
在面向對象程式設計過程中,程式員常常會遇到這種情況:設計一個系統時知道了算法所需的關鍵步驟,而且确定了這些步驟的執行順序,但某些步驟的具體實作還未知,或者說某些步驟的實作與具體的環境相關。
例如,去銀行辦理業務一般要經過以下4個流程:取号、排隊、辦理具體業務、對銀行從業人員進行評分等,其中取号、排隊和對銀行從業人員進行評分的業務對每個客戶是一樣的,可以在父類中實作,但是辦理具體業務卻因人而異,它可能是存款、取款或者轉賬等,可以延遲到子類中實作。
這樣的例子在生活中還有很多,例如,一個人每天會起床、吃飯、做事、睡覺等,其中“做事”的内容每天可能不同。我們把這些規定了流程或格式的執行個體定義成模闆,允許使用者根據自己的需求去更新它,例如,履歷模闆、論文模闆、Word 中模闆檔案等。
以下介紹的模闆方法模式将解決以上類似的問題
2.模式的定義與特點
模闆方法(Template Method)模式的定義如下:定義一個操作中的算法骨架,而将算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行為型模式。
該模式的主要優點如下:
- 它封裝了不變部分,擴充可變部分。它把認為是不變部分的算法封裝到父類中實作,而把可變部分算法由子類繼承實作,便于子類繼續擴充。
- 它在父類中提取了公共的部分代碼,便于代碼複用。
- 部分方法是由子類實作的,是以子類可以通過擴充方式增加相應的功能,符合開閉原則。
該模式的主要缺點如下:
- 對每個不同的實作都需要定義一個子類,這會導緻類的個數增加,系統更加龐大,設計也更加抽象,間接地增加了系統實作的複雜度。
- 父類中的抽象方法由子類實作,子類執行的結果會影響父類的結果,這導緻一種反向的控制結構,它提高了代碼閱讀的難度。
- 由于繼承關系自身的缺點,如果父類添加新的抽象方法,則所有子類都要改一遍。
3.模式的結構與實作
模闆方法模式需要注意抽象類與具體子類之間的協作。它用到了虛函數的多态性技術以及“不用調用我,讓我來調用你”的反向控制技術。現在來介紹它們的基本結構。
1. 模式的結構
模闆方法模式包含以下主要角色。
1)抽象類/抽象模闆(Abstract Class)
抽象模闆類,負責給出一個算法的輪廓和骨架。它由一個模闆方法和若幹個基本方法構成。這些方法的定義如下。
① 模闆方法:定義了算法的骨架,按某種順序調用其包含的基本方法。
② 基本方法:是整個算法中的一個步驟,包含以下幾種類型。
- 抽象方法:在抽象類中聲明,由具體子類實作。
- 具體方法:在抽象類中已經實作,在具體子類中可以繼承或重寫它。
- 鈎子方法:在抽象類中已經實作,包括用于判斷的邏輯方法和需要子類重寫的空方法兩種。
2)具體子類/具體實作(Concrete Class)
具體實作類,實作抽象類中所定義的抽象方法和鈎子方法,它們是一個頂級邏輯的一個組成步驟。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL90zZixWNXpVdWdVYo50MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5cTOxATM0ETM1ETMwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
2. 模式的實作
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("抽象類中的具體方法被調用...");
}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}
//具體子類
class ConcreteClass extends AbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的實作被調用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的實作被調用...");
}
}
抽象類中的具體方法被調用…
抽象方法1的實作被調用…
抽象方法2的實作被調用…
3.模式的應用執行個體
【例1】用模闆方法模式實作出國留學手續設計程式。
分析:出國留學手續一般經過以下流程:索取學校資料,提出入學申請,辦理因私出國護照、出境卡和公證,申請簽證,體檢、訂機票、準備行裝,抵達目标學校等,其中有些業務對各個學校是一樣的,但有些業務因學校不同而不同,是以比較适合用模闆方法模式來實作。
在本執行個體中,我們先定義一個出國留學的抽象類 StudyAbroad,裡面包含了一個模闆方法 TemplateMethod(),該方法中包含了辦理出國留學手續流程中的各個基本方法,其中有些方法的處理由于各國都一樣,是以在抽象類中就可以實作,但有些方法的處理各國是不同的,必須在其具體子類(如美國留學類 StudyInAmerica)中實作。如果再增加一個國家,隻要增加一個子類就可以了,圖 2 所示是其結構圖。
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)了解校園及周邊環境。
4.模式的應用場景
模闆方法模式通常适用于以下場景
- 算法的整體步驟很固定,但其中個别部分易變時,這時候可以使用模闆方法模式,将容易變的部分抽象出來,供子類實作。
- 當多個子類存在公共的行為時,可以将其提取出來并集中到一個公共父類中以避免代碼重複。首先,要識别現有代碼中的不同之處,并且将不同之處分離為新的操作。最後,用一個調用這些新的操作的模闆方法來替換這些不同的代碼。
- 當需要控制子類的擴充時,模闆方法隻在特定點調用鈎子操作,這樣就隻允許在這些點進行擴充。
5.模式的擴充
在模闆方法模式中,基本方法包含:抽象方法、具體方法和鈎子方法,正确使用“鈎子方法”可以使得子類控制父類的行為。如下面例子中,可以通過在具體子類中重寫鈎子方法 HookMethod1() 和 HookMethod2() 來改變抽象父類中的運作結果,其結構圖如圖 3 所示。
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("抽象類中的具體方法被調用...");
}
//鈎子方法1
public void HookMethod1() {
}
//鈎子方法2
public boolean HookMethod2() {
return true;
}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}
//含鈎子方法的具體子類
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的實作被調用…