在面向對象程序設計過程中笤受,程序員常常會遇到這種情況:設計一個系統(tǒng)時知道了算法所需的關鍵步驟壁公,而且確定了這些步驟的執(zhí)行順序,但某些步驟的具體實現(xiàn)還未知紊册,或者說某些步驟的實現(xiàn)與具體的環(huán)境相關快耿。
例如,去銀行辦理業(yè)務一般要經過以下4個流程:取號掀亥、排隊、辦理具體業(yè)務搪花、對銀行工作人員進行評分等,其中取號吮便、排隊和對銀行工作人員進行評分的業(yè)務對每個客戶是一樣的,可以在父類中實現(xiàn)髓需,但是辦理具體業(yè)務卻因人而異房蝉,它可能是存款僚匆、取款或者轉賬等搭幻,可以延遲到子類中實現(xiàn)。
這樣的例子在生活中還有很多檀蹋,例如,一個人每天會起床攻臀、吃飯、做事刨啸、睡覺等识脆,其中“做事”的內容每天可能不同设联。我們把這些規(guī)定了流程或格式的實例定義成模板,允許使用者根據自己的需求去更新它换团,例如,簡歷模板艘包、論文模板耀盗、Word 中模板文件等想虎。
以下介紹的模板方法模式將解決以上類似的問題叛拷。
模式的定義與特點
模板方法(Template Method)模式的定義如下:定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中忿薇,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行為型模式署浩。
該模式的主要優(yōu)點如下。
- 它封裝了不變部分你雌,擴展可變部分二汛。它把認為是不變部分的算法封裝到父類中實現(xiàn)婿崭,而把可變部分算法由子類繼承實現(xiàn)肴颊,便于子類繼續(xù)擴展。
- 它在父類中提取了公共的部分代碼婿着,便于代碼復用。
- 部分方法是由子類實現(xiàn)的提完,因此子類可以通過擴展方式增加相應的功能,符合開閉原則徒欣。
該模式的主要缺點如下。
- 對每個不同的實現(xiàn)都需要定義一個子類打肝,這會導致類的個數(shù)增加,系統(tǒng)更加龐大粗梭,設計也更加抽象。
- 父類中的抽象方法由子類實現(xiàn)断医,子類執(zhí)行的結果會影響父類的結果,這導致一種反向的控制結構酷宵,它提高了代碼閱讀的難度。
模式的結構與實現(xiàn)
模板方法模式需要注意抽象類與具體子類之間的協(xié)作。它用到了虛函數(shù)的多態(tài)性技術以及“不用調用我炕置,讓我來調用你”的反向控制技術。現(xiàn)在來介紹它們的基本結構朴摊。
1. 模式的結構
模板方法模式包含以下主要角色。
(1) 抽象類(Abstract Class):負責給出一個算法的輪廓和骨架口锭。它由一個模板方法和若干個基本方法構成。這些方法的定義如下鹃操。
① 模板方法:定義了算法的骨架春哨,按某種順序調用其包含的基本方法荆隘。
② 基本方法:是整個算法中的一個步驟赴背,包含以下幾種類型。
- 抽象方法:在抽象類中申明凰荚,由具體子類實現(xiàn)。
- 具體方法:在抽象類中已經實現(xiàn)缆毁,在具體子類中可以繼承或重寫它。
- 鉤子方法:在抽象類中已經實現(xiàn)积锅,包括用于判斷的邏輯方法和需要子類重寫的空方法兩種。
(2) 具體子類(Concrete Class):實現(xiàn)抽象類中所定義的抽象方法和鉤子方法缚陷,它們是一個頂級邏輯的一個組成步驟。
模板方法模式的結構圖如圖 1 所示箫爷。
圖1 模板方法模式的結構圖
2. 模式的實現(xiàn)
模板方法模式的代碼如下:
1. package templateMethod;
2. public class TemplateMethodPattern
3. {
4. public static void main(String[] args)
5. {
6. AbstractClass tm=new ConcreteClass();
7. tm.TemplateMethod();
8. }
9. }
10. //抽象類
11. abstract class AbstractClass
12. {
13. public void TemplateMethod() //模板方法
14. {
15. SpecificMethod();
16. abstractMethod1();
17. abstractMethod2();
18. }
19. public void SpecificMethod() //具體方法
20. {
21. System.out.println("抽象類中的具體方法被調用...");
22. }
23. public abstract void abstractMethod1(); //抽象方法1
24. public abstract void abstractMethod2(); //抽象方法2
25. }
26. //具體子類
27. class ConcreteClass extends AbstractClass
28. {
29. public void abstractMethod1()
30. {
31. System.out.println("抽象方法1的實現(xiàn)被調用...");
32. }
33. public void abstractMethod2()
34. {
35. System.out.println("抽象方法2的實現(xiàn)被調用...");
36. }
37. }
程序的運行結果如下:
<pre class="info-box">抽象類中的具體方法被調用...
抽象方法1的實現(xiàn)被調用...
抽象方法2的實現(xiàn)被調用...</pre>
模式的應用實例
【例1】用模板方法模式實現(xiàn)出國留學手續(xù)設計程序虎锚。
分析:出國留學手續(xù)一般經過以下流程:索取學校資料硫痰,提出入學申請窜护,辦理因私出國護照、出境卡和公證柱徙,申請簽證,體檢敌完、訂機票、準備行裝滨溉,抵達目標學校等长赞,其中有些業(yè)務對各個學校是一樣的晦攒,但有些業(yè)務因學校不同而不同涧卵,所以比較適合用模板方法模式來實現(xiàn)。
在本實例中柳恐,我們先定義一個出國留學的抽象類 StudyAbroad,里面包含了一個模板方法 TemplateMethod()乐设,該方法中包含了辦理出國留學手續(xù)流程中的各個基本方法,其中有些方法的處理由于各國都一樣蠕啄,所以在抽象類中就可以實現(xiàn),但有些方法的處理各國是不同的歼跟,必須在其具體子類(如美國留學類 StudyInAmerica)中實現(xiàn)。如果再增加一個國家哈街,只要增加一個子類就可以了,圖 2 所示是其結構圖骚秦。
圖2 出國留學手續(xù)設計程序的結構圖
程序代碼如下:
1. package templateMethod;
2. public class StudyAbroadProcess
3. {
4. public static void main(String[] args)
5. {
6. StudyAbroad tm=new StudyInAmerica();
7. tm.TemplateMethod();
8. }
9. }
10. //抽象類: 出國留學
11. abstract class StudyAbroad
12. {
13. public void TemplateMethod() //模板方法
14. {
15. LookingForSchool(); //索取學校資料
16. ApplyForEnrol(); //入學申請
17. ApplyForPassport(); //辦理因私出國護照、出境卡和公證
18. ApplyForVisa(); //申請簽證
19. ReadyGoAbroad(); //體檢硬梁、訂機票胞得、準備行裝
20. Arriving(); //抵達
21. }
22. public void ApplyForPassport()
23. {
24. System.out.println("三.辦理因私出國護照荧止、出境卡和公證:");
25. System.out.println(" 1)持錄取通知書阶剑、本人戶口簿或身份證向戶口所在地公安機關申請辦理因私出國護照和出境卡。");
26. System.out.println(" 2)辦理出生公證書个扰,學歷葱色、學位和成績公證,經歷證書苍狰,親屬關系公證,經濟擔保公證淋昭。");
27. }
28. public void ApplyForVisa()
29. {
30. System.out.println("四.申請簽證:");
31. System.out.println(" 1)準備申請國外境簽證所需的各種資料,包括個人學歷英融、成績單、工作經歷的證明驶悟;個人及家庭收入、資金和財產證明痕鳍;家庭成員的關系證明等;");
32. System.out.println(" 2)向擬留學國家駐華使(領)館申請入境簽證笼呆。申請時需按要求填寫有關表格,遞交必需的證明材料诗赌,繳納簽證。有的國家(比如美國境肾、英國、加拿大等)在申請簽證時會要求申請人前往使(領)館進行面試偶宫。");
33. }
34. public void ReadyGoAbroad()
35. {
36. System.out.println("五.體檢、訂機票纯趋、準備行裝:");
37. System.out.println(" 1)進行身體檢查冷离、免疫檢查和接種傳染病疫苗吵冒;");
38. System.out.println(" 2)確定機票時間西剥、航班和轉機地點。");
39. }
40. public abstract void LookingForSchool();//索取學校資料
41. public abstract void ApplyForEnrol(); //入學申請
42. public abstract void Arriving(); //抵達
43. }
44. //具體子類: 美國留學
45. class StudyInAmerica extends StudyAbroad
46. {
47. @Override
48. public void LookingForSchool()
49. {
50. System.out.println("一.索取學校以下資料:");
51. System.out.println(" 1)對留學意向國家的政治瞭空、經濟、文化背景和教育體制南捂、學術水平進行較為全面的了解;");
52. System.out.println(" 2)全面了解和掌握國外學校的情況溺健,包括歷史、學費鞭缭、學制魏颓、專業(yè)缚去、師資配備琼开、教學設施、學術地位、學生人數(shù)等躏精;");
53. System.out.println(" 3)了解該學校的住宿、交通矗烛、醫(yī)療保險情況如何箩溃;");
54. System.out.println(" 4)該學校在中國是否有授權代理招生的留學中介公司瞭吃?");
55. System.out.println(" 5)掌握留學簽證情況涣旨;");
56. System.out.println(" 6)該國政府是否允許留學生合法打工?");
57. System.out.println(" 8)畢業(yè)之后可否移民霹陡?");
58. System.out.println(" 9)文憑是否受到我國認可?");
59. }
60. @Override
61. public void ApplyForEnrol()
62. {
63. System.out.println("二.入學申請:");
64. System.out.println(" 1)填寫報名表攒霹;");
65. System.out.println(" 2)將報名表、個人學歷證明催束、最近的學習成績單伏社、推薦信泣崩、個人簡歷洛口、托缚Γ或雅思語言考試成績單等資料寄往所申請的學校;");
66. System.out.println(" 3)為了給簽證辦理留有充裕的時間妨马,建議越早申請越好,一般提前1年就比較從容烘跺。");
67. }
68. @Override
69. public void Arriving()
70. {
71. System.out.println("六.抵達目標學校:");
72. System.out.println(" 1)安排住宿;");
73. System.out.println(" 2)了解校園及周邊環(huán)境滤淳。");
74. }
75. }
程序的運行結果如下:
<pre class="info-box">一.索取學校以下資料:
1)對留學意向國家的政治、經濟铺敌、文化背景和教育體制汇歹、學術水平進行較為全面的了解偿凭;
2)全面了解和掌握國外學校的情況,包括歷史弯囊、學費系忙、學制、專業(yè)、師資配備商蕴、教學設施、學術地位吼过、學生人數(shù)等;
3)了解該學校的住宿盗忱、交通、醫(yī)療保險情況如何扇谣;
4)該學校在中國是否有授權代理招生的留學中介公司?
5)掌握留學簽證情況罐寨;
6)該國政府是否允許留學生合法打工序矩?
8)畢業(yè)之后可否移民鸯绿?
9)文憑是否受到我國認可簸淀?
二.入學申請:
1)填寫報名表;
2)將報名表租幕、個人學歷證明、最近的學習成績單劲绪、推薦信盆赤、個人簡歷蝎宇、托福或雅思語言考試成績單等資料寄往所申請的學校姥芥;
3)為了給簽證辦理留有充裕的時間,建議越早申請越好凉唐,一般提前1年就比較從容。
三.辦理因私出國護照台囱、出境卡和公證:
1)持錄取通知書、本人戶口簿或身份證向戶口所在地公安機關申請辦理因私出國護照和出境卡咱娶。
2)辦理出生公證書,學歷膘侮、學位和成績公證,經歷證書琼了,親屬關系公證夫晌,經濟擔保公證雕薪。
四.申請簽證:
1)準備申請國外境簽證所需的各種資料晓淀,包括個人學歷、成績單凶掰、工作經歷的證明;個人及家庭收入锄俄、資金和財產證明勺拣;家庭成員的關系證明等;
2)向擬留學國家駐華使(領)館申請入境簽證药有。申請時需按要求填寫有關表格苹丸,遞交必需的證明材料苇经,繳納簽證。有的國家(比如美國扇单、英國、加拿大等)在申請簽證時會要求申請人前往使(領)館進行面試蜘澜。
五.體檢、訂機票鄙信、準備行裝:
1)進行身體檢查、免疫檢查和接種傳染病疫苗银受;
2)確定機票時間、航班和轉機地點宾巍。
六.抵達目標學校:
1)安排住宿赖淤;
2)了解校園及周邊環(huán)境蜀漆。</pre>
模式的應用場景
模板方法模式通常適用于以下場景咱旱。
- 算法的整體步驟很固定,但其中個別部分易變時吐限,這時候可以使用模板方法模式,將容易變的部分抽象出來诸典,供子類實現(xiàn)描函。
- 當多個子類存在公共的行為時狐粱,可以將其提取出來并集中到一個公共父類中以避免代碼重復。首先肌蜻,要識別現(xiàn)有代碼中的不同之處,并且將不同之處分離為新的操作蒋搜。最后判莉,用一個調用這些新的操作的模板方法來替換這些不同的代碼育谬。
- 當需要控制子類的擴展時,模板方法只在特定點調用鉤子操作膛檀,這樣就只允許在這些點進行擴展。
模式的擴展
在模板方法模式中宿刮,基本方法包含:抽象方法、具體方法和鉤子方法胡桃,正確使用“鉤子方法”可以使得子類控制父類的行為。如下面例子中翠胰,可以通過在具體子類中重寫鉤子方法 HookMethod1() 和 HookMethod2() 來改變抽象父類中的運行結果自脯,其結構圖如圖 3 所示之景。
圖3 含鉤子方法的模板方法模式的結構圖
程序代碼如下:
1. package templateMethod;
2. public class HookTemplateMethod
3. {
4. public static void main(String[] args)
5. {
6. HookAbstractClass tm=new HookConcreteClass();
7. tm.TemplateMethod();
8. }
9. }
10. //含鉤子方法的抽象類
11. abstract class HookAbstractClass
12. {
13. public void TemplateMethod() //模板方法
14. {
15. abstractMethod1();
16. HookMethod1();
17. if(HookMethod2())
18. {
19. SpecificMethod();
20. }
21. abstractMethod2();
22. }
23. public void SpecificMethod() //具體方法
24. {
25. System.out.println("抽象類中的具體方法被調用...");
26. }
27. public void HookMethod1(){} //鉤子方法1
28. public boolean HookMethod2() //鉤子方法2
29. {
30. return true;
31. }
32. public abstract void abstractMethod1(); //抽象方法1
33. public abstract void abstractMethod2(); //抽象方法2
34. }
35. //含鉤子方法的具體子類
36. class HookConcreteClass extends HookAbstractClass
37. {
38. public void abstractMethod1()
39. {
40. System.out.println("抽象方法1的實現(xiàn)被調用...");
41. }
42. public void abstractMethod2()
43. {
44. System.out.println("抽象方法2的實現(xiàn)被調用...");
45. }
46. public void HookMethod1()
47. {
48. System.out.println("鉤子方法1被重寫...");
49. }
50. public boolean HookMethod2()
51. {
52. return false;
53. }
54. }
程序的運行結果如下:
<pre class="info-box">抽象方法1的實現(xiàn)被調用...
鉤子方法1被重寫...
抽象方法2的實現(xiàn)被調用...</pre>
如果鉤子方法 HookMethod1() 和鉤子方法 HookMethod2() 的代碼改變锻狗,則程序的運行結果也會改變焕参。