點贊再看唇兑,養(yǎng)成習(xí)慣,公眾號搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章。本文 GitHub org_hejianhui/JavaStudy 已收錄腕侄,有我的系列文章时呀。
前言
- 23種設(shè)計模式速記
- 單例(singleton)模式
- 工廠方法(factory method)模式
- 抽象工廠(abstract factory)模式
- 建造者/構(gòu)建器(builder)模式
- 原型(prototype)模式
- 享元(flyweight)模式
- 外觀(facade)模式
- 適配器(adapter)模式
- 裝飾(decorator)模式
- 觀察者(observer)模式
- 策略(strategy)模式
- 橋接(bridge)模式
- 持續(xù)更新中......
23種設(shè)計模式快速記憶的請看上面第一篇,本篇和大家一起來學(xué)習(xí)模板方法模式相關(guān)內(nèi)容晴玖。
模式定義
定義一個操作的算法骨架读存,而將一些步驟延遲到子類中为流。Template Method 使得子類可以不改變一個算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
在不改變模板結(jié)構(gòu)的前提下在子類中重新定義模板中的內(nèi)容让簿。
模板方法模式是基于”繼承“的敬察;
模式實現(xiàn)如下:
package com.niuh.designpattern.templatemethod.v1;
/**
* 模板方法模式
*/
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("抽象類中的具體方法被調(diào)用...");
}
//抽象方法1
public abstract void abstractMethod1();
//抽象方法2
public abstract void abstractMethod2();
}
//具體子類
class ConcreteClass extends AbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的實現(xiàn)被調(diào)用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的實現(xiàn)被調(diào)用...");
}
}
輸出結(jié)果如下:
抽象類中的具體方法被調(diào)用...
抽象方法1的實現(xiàn)被調(diào)用...
抽象方法2的實現(xiàn)被調(diào)用...
解決的問題
- 提高代碼復(fù)用性
將相同部分的代碼放在抽象的父類中,而將不同的代碼放入不同的子類中
- 實現(xiàn)了反向控制
通過一個父類調(diào)用其子類的操作尔当,通過對子類的具體實現(xiàn)擴展不同的行為莲祸,實現(xiàn)了反向控制 & 符合“開閉原則”
在面向?qū)ο蟪绦蛟O(shè)計過程中,常常會遇到這種情況:設(shè)計一個系統(tǒng)時知道了算法所需的關(guān)鍵步驟椭迎,而且確定了這些步驟的執(zhí)行順序锐帜,但某些步驟的具體實現(xiàn)還未知,或者說某些步驟的實現(xiàn)與具體的環(huán)境相關(guān)侠碧。
模式組成
模板方法模式需要注意抽象類與具體子類之間的協(xié)作抹估。它用到了虛函數(shù)的多態(tài)性技術(shù)以及“不用調(diào)用我,讓我來調(diào)用你”的反向控制技術(shù)∨担現(xiàn)在來介紹它們的基本結(jié)構(gòu)药蜻。
實例說明
實例概況
用模板方法模式實現(xiàn)出國留學(xué)手續(xù)設(shè)計程序。
分析:出國留學(xué)手續(xù)一般經(jīng)過以下流程:索取學(xué)校資料替饿,提出入學(xué)申請语泽,辦理因私出國護(hù)照、出境卡和公證视卢,申請簽證踱卵,體檢、訂機票据过、準(zhǔn)備行裝惋砂,抵達(dá)目標(biāo)學(xué)校等,其中有些業(yè)務(wù)對各個學(xué)校是一樣的绳锅,但有些業(yè)務(wù)因?qū)W校不同而不同西饵,所以比較適合用模板方法模式來實現(xiàn)。
在本實例中鳞芙,我們先定義一個出國留學(xué)的抽象類 StudyAbroad眷柔,里面包含了一個模板方法 TemplateMethod(),該方法中包含了辦理出國留學(xué)手續(xù)流程中的各個基本方法原朝,其中有些方法的處理由于各國都一樣驯嘱,所以在抽象類中就可以實現(xiàn),但有些方法的處理各國是不同的喳坠,必須在其具體子類(如美國留學(xué)類 StudyInAmerica)中實現(xiàn)鞠评。如果再增加一個國家,只要增加一個子類就可以了丙笋,圖 2 所示是其結(jié)構(gòu)圖谢澈。
使用步驟
步驟1:定義一個出國留學(xué)的抽象類 StudyAbroad
abstract class StudyAbroad {
//模板方法
public void templateMethod() {
lookingForSchool(); //索取學(xué)校資料
applyForEnrol(); //入學(xué)申請
applyForPassport(); //辦理因私出國護(hù)照煌贴、出境卡和公證
applyForVisa(); //申請簽證
readyGoAbroad(); //體檢、訂機票锥忿、準(zhǔn)備行裝
arriving(); //抵達(dá)
}
public void applyForPassport() {
System.out.println("三.辦理因私出國護(hù)照牛郑、出境卡和公證:");
System.out.println(" 1)持錄取通知書、本人戶口簿或身份證向戶口所在地公安機關(guān)申請辦理因私出國護(hù)照和出境卡敬鬓。");
System.out.println(" 2)辦理出生公證書淹朋,學(xué)歷、學(xué)位和成績公證钉答,經(jīng)歷證書础芍,親屬關(guān)系公證,經(jīng)濟擔(dān)保公證数尿。");
}
public void applyForVisa() {
System.out.println("四.申請簽證:");
System.out.println(" 1)準(zhǔn)備申請國外境簽證所需的各種資料仑性,包括個人學(xué)歷、成績單右蹦、工作經(jīng)歷的證明诊杆;個人及家庭收入、資金和財產(chǎn)證明何陆;家庭成員的關(guān)系證明等晨汹;");
System.out.println(" 2)向擬留學(xué)國家駐華使(領(lǐng))館申請入境簽證。申請時需按要求填寫有關(guān)表格贷盲,遞交必需的證明材料淘这,繳納簽證。有的國家(比如美國巩剖、英國铝穷、加拿大等)在申請簽證時會要求申請人前往使(領(lǐng))館進(jìn)行面試。");
}
public void readyGoAbroad() {
System.out.println("五.體檢佳魔、訂機票氧骤、準(zhǔn)備行裝:");
System.out.println(" 1)進(jìn)行身體檢查、免疫檢查和接種傳染病疫苗吃引;");
System.out.println(" 2)確定機票時間、航班和轉(zhuǎn)機地點刽锤。");
}
//索取學(xué)校資料
public abstract void lookingForSchool();
//入學(xué)申請
public abstract void applyForEnrol();
//抵達(dá)
public abstract void arriving();
}
步驟2:定義具體子類: 美國留學(xué)
class StudyInAmerica extends StudyAbroad {
@Override
public void lookingForSchool() {
System.out.println("一.索取學(xué)校以下資料:");
System.out.println(" 1)對留學(xué)意向國家的政治镊尺、經(jīng)濟、文化背景和教育體制并思、學(xué)術(shù)水平進(jìn)行較為全面的了解庐氮;");
System.out.println(" 2)全面了解和掌握國外學(xué)校的情況,包括歷史宋彼、學(xué)費弄砍、學(xué)制仙畦、專業(yè)、師資配備音婶、教學(xué)設(shè)施慨畸、學(xué)術(shù)地位、學(xué)生人數(shù)等衣式;");
System.out.println(" 3)了解該學(xué)校的住宿寸士、交通钩骇、醫(yī)療保險情況如何砾隅;");
System.out.println(" 4)該學(xué)校在中國是否有授權(quán)代理招生的留學(xué)中介公司利职?");
System.out.println(" 5)掌握留學(xué)簽證情況准脂;");
System.out.println(" 6)該國政府是否允許留學(xué)生合法打工馏段?");
System.out.println(" 8)畢業(yè)之后可否移民耘成?");
System.out.println(" 9)文憑是否受到我國認(rèn)可且预?");
}
@Override
public void applyForEnrol() {
System.out.println("二.入學(xué)申請:");
System.out.println(" 1)填寫報名表收奔;");
System.out.println(" 2)將報名表荧飞、個人學(xué)歷證明凡人、最近的學(xué)習(xí)成績單、推薦信垢箕、個人簡歷划栓、托福或雅思語言考試成績單等資料寄往所申請的學(xué)校条获;");
System.out.println(" 3)為了給簽證辦理留有充裕的時間忠荞,建議越早申請越好,一般提前1年就比較從容帅掘。");
}
@Override
public void arriving() {
System.out.println("六.抵達(dá)目標(biāo)學(xué)校:");
System.out.println(" 1)安排住宿委煤;");
System.out.println(" 2)了解校園及周邊環(huán)境。");
}
}
步驟3:測試結(jié)果
/**
* 模板方法模式案例
* <p>
* 用模板方法模式實現(xiàn)出國留學(xué)手續(xù)設(shè)計程序
*/
public class TemplateMethodPattern {
public static void main(String[] args) {
StudyAbroad tm=new StudyInAmerica();
tm.templateMethod();
}
}
輸出結(jié)果
一.索取學(xué)校以下資料:
1)對留學(xué)意向國家的政治修档、經(jīng)濟碧绞、文化背景和教育體制、學(xué)術(shù)水平進(jìn)行較為全面的了解吱窝;
2)全面了解和掌握國外學(xué)校的情況讥邻,包括歷史、學(xué)費院峡、學(xué)制兴使、專業(yè)、師資配備照激、教學(xué)設(shè)施发魄、學(xué)術(shù)地位、學(xué)生人數(shù)等;
3)了解該學(xué)校的住宿励幼、交通汰寓、醫(yī)療保險情況如何;
4)該學(xué)校在中國是否有授權(quán)代理招生的留學(xué)中介公司苹粟?
5)掌握留學(xué)簽證情況有滑;
6)該國政府是否允許留學(xué)生合法打工?
8)畢業(yè)之后可否移民六水?
9)文憑是否受到我國認(rèn)可俺孙?
二.入學(xué)申請:
1)填寫報名表;
2)將報名表掷贾、個人學(xué)歷證明睛榄、最近的學(xué)習(xí)成績單、推薦信想帅、個人簡歷场靴、托福或雅思語言考試成績單等資料寄往所申請的學(xué)校港准;
3)為了給簽證辦理留有充裕的時間旨剥,建議越早申請越好,一般提前1年就比較從容浅缸。
三.辦理因私出國護(hù)照轨帜、出境卡和公證:
1)持錄取通知書、本人戶口簿或身份證向戶口所在地公安機關(guān)申請辦理因私出國護(hù)照和出境卡衩椒。
2)辦理出生公證書蚌父,學(xué)歷、學(xué)位和成績公證毛萌,經(jīng)歷證書苟弛,親屬關(guān)系公證,經(jīng)濟擔(dān)保公證阁将。
四.申請簽證:
1)準(zhǔn)備申請國外境簽證所需的各種資料膏秫,包括個人學(xué)歷、成績單做盅、工作經(jīng)歷的證明缤削;個人及家庭收入、資金和財產(chǎn)證明吹榴;家庭成員的關(guān)系證明等僻他;
2)向擬留學(xué)國家駐華使(領(lǐng))館申請入境簽證。申請時需按要求填寫有關(guān)表格腊尚,遞交必需的證明材料,繳納簽證满哪。有的國家(比如美國婿斥、英國劝篷、加拿大等)在申請簽證時會要求申請人前往使(領(lǐng))館進(jìn)行面試。
五.體檢民宿、訂機票娇妓、準(zhǔn)備行裝:
1)進(jìn)行身體檢查、免疫檢查和接種傳染病疫苗活鹰;
2)確定機票時間哈恰、航班和轉(zhuǎn)機地點。
六.抵達(dá)目標(biāo)學(xué)校:
1)安排住宿志群;
2)了解校園及周邊環(huán)境着绷。
優(yōu)點
- 它封裝了不變部分,擴展可變部分锌云。它把認(rèn)為是不變部分的算法封裝到父類中實現(xiàn)荠医,而把可變部分算法由子類繼承實現(xiàn),便于子類繼續(xù)擴展桑涎。
- 它在父類中提取了公共的部分代碼彬向,便于代碼復(fù)用。
- 部分方法是由子類實現(xiàn)的攻冷,因此子類可以通過擴展方式增加相應(yīng)的功能娃胆,符合開閉原則。
缺點
- 對每個不同的實現(xiàn)都需要定義一個子類等曼,這會導(dǎo)致類的個數(shù)增加里烦,系統(tǒng)更加龐大,設(shè)計也更加抽象涉兽。
- 父類中的抽象方法由子類實現(xiàn)招驴,子類執(zhí)行的結(jié)果會影響父類的結(jié)果,這導(dǎo)致一種反向的控制結(jié)構(gòu)枷畏,它提高了代碼閱讀的難度别厘。
應(yīng)用場景
- 當(dāng)你想讓客戶端只擴展算法的特定步驟,而不是整個算法或其結(jié)構(gòu)時拥诡,請使用Template Method模式触趴;
- 當(dāng)你有幾個類包含幾乎相同的算法,但有一些細(xì)微的差異時渴肉,請使用此模式冗懦。
模式的擴展
在模板方法模式中,基本方法包含:抽象方法仇祭、具體方法和鉤子方法披蕉,正確使用“鉤子方法”可以使得子類控制父類的行為。如下面例子中,可以通過在具體子類中重寫鉤子方法 HookMethod1() 和 HookMethod2() 來改變抽象父類中的運行結(jié)果没讲,其結(jié)構(gòu)圖如下圖所示:
代碼如下:
package com.niuh.designpattern.templatemethod.v3;
/**
* 含鉤子方法的模板方法模式
*/
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("抽象類中的具體方法被調(diào)用...");
}
//鉤子方法1
public void HookMethod1() {
}
//鉤子方法2
public boolean HookMethod2() {
return true;
}
public abstract void abstractMethod1(); //抽象方法1
public abstract void abstractMethod2(); //抽象方法2
}
//含鉤子方法的具體子類
class HookConcreteClass extends HookAbstractClass {
public void abstractMethod1() {
System.out.println("抽象方法1的實現(xiàn)被調(diào)用...");
}
public void abstractMethod2() {
System.out.println("抽象方法2的實現(xiàn)被調(diào)用...");
}
public void HookMethod1() {
System.out.println("鉤子方法1被重寫...");
}
public boolean HookMethod2() {
return false;
}
}
輸出結(jié)果如下:
抽象方法1的實現(xiàn)被調(diào)用...
鉤子方法1被重寫...
抽象方法2的實現(xiàn)被調(diào)用...
源碼中的應(yīng)用
#Servlet Api
javax.servlet.http.HttpServlet
#Spring
org.springframework.web.servlet.mvc.AbstractController
......
模板方法模式在Servlet中的應(yīng)用
Servle除了要在web.xml做相應(yīng)的配置外眯娱,還需繼承一個叫HttpServlet的抽象類。HttpService類提供了一個service()方法爬凑,這個方法調(diào)用七個do方法中的一個或幾個徙缴,完成對客戶端調(diào)用的響應(yīng)。這些do方法需要由HttpServlet的具體子類提供嘁信,因此這是典型的模板方法模式于样。下面是service()方法的源代碼:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
這個service()方法也可以被子類置換掉。下面給出一個簡單的Servlet例子:
從上面的類圖可以看出潘靖,TestServlet類是HttpServlet類的子類穿剖,并且置換掉了父類的兩個方法:
doGet()
和doPost()
。
public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("using the GET method");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("using the POST method");
}
}
從上面的例子可以看出這是一個典型的模板方法模式秘豹。
-
HttpServlet擔(dān)任抽象模板角色
- 模板方法:由service()方法擔(dān)任携御。
- 基本方法:由doPost()、doGet()等方法擔(dān)任既绕。
-
TestServlet擔(dān)任具體模板角色
- TestServlet置換掉了父類HttpServlet中七個基本方法中的其中兩個啄刹,分別是doGet()和doPost()。
PS:以上代碼提交在 Github :https://github.com/Niuh-Study/niuh-designpatterns.git
文章持續(xù)更新凄贩,可以公眾號搜一搜「 一角錢技術(shù) 」第一時間閱讀誓军, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄,歡迎 Star疲扎。