定義
模板方法模式是類的行為模式。準(zhǔn)備一個抽象類蔫慧,將部分邏輯以具體方法以及具體構(gòu)造函數(shù)的形式實現(xiàn)挠乳,然后聲明一些抽象方法來迫使子類實現(xiàn)剩余的邏輯。不同的子類可以以不同的方式實現(xiàn)這些抽象方法姑躲,從而對剩余的邏輯有不同的實現(xiàn)睡扬。這就是模板方法模式的用意。
模板方法模式的結(jié)構(gòu)
模板方法模式是所有模式中最為常見的幾個模式之一黍析,是基于繼承的代碼服用的基本技術(shù)卖怜。
模板方法模式需要開發(fā)抽象類和具體子類的設(shè)計師之間的協(xié)作。一個設(shè)計師負責(zé)給出一個算法的輪廓和骨架阐枣,另一些設(shè)計師則負責(zé)給出這個算法的各個邏輯步驟马靠。代表這些具體邏輯步驟的方法稱作基本方法(primitive method
)奄抽; 而將這些基本方法匯總起來的方法叫做模板方法(template method
),這個設(shè)計模式的名字就是由此而來虑粥。
模板方法所代表的行為稱為頂級行為如孝,其邏輯稱為頂級邏輯。模板方法模式的靜態(tài)結(jié)構(gòu)圖如下所示:
這里涉及到兩個角色:
抽象模板(Abstract Template)角色娩贷,具有如下責(zé)任:
- 定義了一個或多個抽象操作以便讓子類實現(xiàn)魁巩。這些抽象操作叫做基本操作。他們是一個頂級邏輯的組成結(jié)構(gòu)哗蜈。
- 定義并實現(xiàn)了一個模板方法纽竣。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架储笑,而邏輯的組成步驟在相應(yīng)的抽象操作中甜熔,推遲到子類實現(xiàn)。頂級邏輯也有可能調(diào)用一些具體方法突倍。
具體模板(Concrete Template)角色腔稀,具有如下責(zé)任:
- 實現(xiàn)父類所定義的一個或多個抽象方法,他們是一個頂級邏輯的組成步驟羽历。
- 每一個抽象模板角色都可以有任意多個具體模板角色與之對應(yīng)焊虏,而每一個具體模板角色都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現(xiàn),從而使得頂級邏輯的實現(xiàn)各不相同秕磷。
示例代碼
抽象模板角色類诵闭,abstractMethod()
、hookMethod()
等基本方法是頂級邏輯的組成步驟澎嚣,這個頂級邏輯是由templateMethod()
方法代表疏尿。
public abstract class AbstractTemplate {
/**
* 模板方法
*/
public void templateMethod() {
//調(diào)用基本方法
abstractMethod();
hookMethod();
concreteMethod();
}
/**
* 基本方法的聲明(由子類實現(xiàn))
*/
protected abstract void abstractMethod();
/**
* 基本方法(空方法)
*/
protected void hookMethod(){}
/**
* 基本方法(已經(jīng)實現(xiàn))
*/
protected final void concreteMethod() {
// TODO 業(yè)務(wù)相關(guān)的代碼
}
}
具體模板角色類,實現(xiàn)了父類所聲明的基本方法易桃,abstractMethod()
方法代表的就是強制子類實現(xiàn)的剩余邏輯褥琐,而hookMethod()
方法是可以選擇實現(xiàn)的邏輯,不是必須實現(xiàn)的颈抚。
public class ConcreteTemplate extends AbstractTemplate {
/**
* 對父類基本方法的實現(xiàn)
*/
protected void abstractMethod() {
// TODO 業(yè)務(wù)相關(guān)的代碼
}
/**
* 重寫父類的方法
*/
@Override
protected void hookMethod() {
// TODO 業(yè)務(wù)相關(guān)的代碼
}
}
模板方法模式的關(guān)鍵是:子類可以置換掉父類的可變部分踩衩,但是子類卻不可以改變模板方法所代表的頂級邏輯。
每當(dāng)定義一個新的子類時贩汉,不要按照控制流程的思路去想驱富,而應(yīng)當(dāng)按照“責(zé)任”的思路去想。換而言之匹舞,應(yīng)當(dāng)考慮哪些操作是必需置換掉的褐鸥,哪些操作是可以置換掉的,以及哪些操作時不可以置換掉的赐稽。使用模板模式可以使這些責(zé)任變得清晰叫榕。
模板方法模式中的方法
模板方法模式中的方法可以分為兩大類:模板方法和基本方法浑侥。
模板方法
一個模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個總算法或者一個總行為的方法晰绎。
一個抽象類可以有任意多個模板方法寓落,而不限于一個。每一個模板方法都可以調(diào)用任意多個具體方法荞下。
基本方法
基本方法又可以分為三類:抽象方法(Abstract Method)伶选、具體方法(Concrete Method)和鉤子方法(Hook Method)。
-
抽象方法:一個抽象方法由抽象類聲明尖昏,由具體子類實現(xiàn)仰税。在Java語言里抽象方法以
Abstract
關(guān)鍵字標(biāo)示。 - 具體方法:一個具體方法由抽象類聲明并實現(xiàn)抽诉,而子類并不實現(xiàn)或置換陨簇。
- 鉤子方法:一個鉤子方法由抽象類聲明并實現(xiàn),而子類會加以拓展迹淌。通常抽象類給出的實現(xiàn)是一個空實現(xiàn)河绽,作為方法的默認實現(xiàn)。
在上面的例子中唉窃,AbstractTemplate
是一個抽象類葵姥,她有三個方法,其中abstractMethod()
是一個抽象方法句携,它由抽象類聲明為抽象方法,并由子類實現(xiàn)允乐;hookMethod()
是一個鉤子方法矮嫉,它由抽象類聲明并提供默認實現(xiàn),并且由子類置換掉牍疏;concreteMethod()
是一個具體方法蠢笋,它由抽象類聲明并實現(xiàn)。
默認鉤子方法
一個鉤子方法常常由抽象類給出一個空實現(xiàn)作為此方法的默認實現(xiàn)鳞陨。這種空的鉤子方法叫做“Do Nothing Hook
”昨寞。顯然,這種默認鉤子方法在缺省適配模式里面已經(jīng)見過了厦滤,一個缺省適配模式講的是一個類為一個接口提供一個默認的空實現(xiàn)援岩,從而使得缺省適配類的子類不必像實現(xiàn)接口那樣必須給出所有方法的實現(xiàn),因為通常一個具體類并不需要所有的方法掏导。
命名規(guī)則
命名規(guī)則是設(shè)計師之間可以溝通的管道之一享怀,使用恰當(dāng)?shù)拿?guī)則可以幫助不同設(shè)計師之間的溝通。
鉤子方法的命名應(yīng)當(dāng)以do
開始趟咆,這是熟悉設(shè)計模式的Java開發(fā)人員的標(biāo)準(zhǔn)做法添瓷。在上面的例子中梅屉,鉤子方法hookMethod()
應(yīng)當(dāng)以do
開頭;在HttpServlet
類中鳞贷,也遵從這一命名規(guī)則坯汤,例如doGet()
、doPost()
等方法搀愧。
使用場景
考慮一個計算存款利息的例子惰聂。假設(shè)系統(tǒng)需要支持兩種存款賬號,即貨幣市場(Money Market)賬號和定期存款(Certificate of Deposite)賬號妈橄。這兩種賬號的存款利息是不同的庶近,因此,在計算 一個存戶的存款利息額時眷蚓,必須區(qū)分兩種不同的賬號類型鼻种。
這個系統(tǒng)的總行為應(yīng)當(dāng)是計算出利息,這就決定了作為一個模板方法模式的頂級邏輯應(yīng)當(dāng)是利息計算沙热。由于利息計算涉及到兩個步驟: 一個基本方法給出賬號種類叉钥,另一個基本方法給出利息百分比。這兩個基本方法構(gòu)成具體邏輯篙贸,因為賬號的類型不同投队,所以具體邏輯會有所不同。
顯然爵川,系統(tǒng)需要一個抽象角色給出頂級行為的實現(xiàn)敷鸦,而將兩個作為細節(jié)步驟的基本方法留給具體子類實現(xiàn)。由于需要考慮的賬號有兩種:一種是貨幣市場賬號寝贡,另一種是定期存款賬號扒披。系統(tǒng)的類結(jié)構(gòu)如下圖所示:
示例代碼
抽象模板角色類
public abstract class Account {
/**
* 模板方法,計算利息數(shù)額
* @return 返回利息數(shù)額
*/
public final double calculateInterest() {
//利息百分比
double interestRate = doCalculateInterestRate();
//賬號類型
String accountType = doCalculateAccountType();
//賬號內(nèi)存款
double amount = calculateAmount(accountType);
//返回利息數(shù)額
return amount * interestRate;
}
/**
* 基本方法圃泡,留給子類實現(xiàn)
* @return
*/
protected abstract String doCalculateAccountType();
/**
* 基本方法碟案,留給子類實現(xiàn)
* @return
*/
protected abstract double doCalculateInterestRate();
/**
* 基本方法,已經(jīng)實現(xiàn)
* @param accountType
* @return
*/
private double calculateAmount(String accountType) {
//TODO 省略相關(guān)的業(yè)務(wù)邏輯
return 1234.00;
}
}
具體模板角色類
public class MoneyMarketAccount extends Account {
@Override
protected String doCalculateAccountType() {
return "Money Market";
}
@Override
protected double doCalculateInterestRate() {
return 0.045;
}
}
public class CDAccount extends Account {
@Override
protected String doCalculateAccountType() {
return "Certificate of Deposite";
}
@Override
protected double doCalculateInterestRate() {
return 0.06;
}
}
客戶端類
public class Client {
public static void main(String[] args) {
Account account = new MoneyMarketAccount();
System.out.println("貨幣市場賬號的利息數(shù)額為:" + account.calculateInterest());
account = new CDAccount();
System.out.println("定期賬號的利息數(shù)額為:" + account.calculateInterest());
}
}
模板方法模式在Servlet中的應(yīng)用
使用過Servlet的人都清楚颇蜡,除了要在web.xml做相應(yīng)的配置之外价说,還需要繼承一個叫做HttpServlet
的抽象類。HttpServlet
類中风秤,提供了一個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) {
// 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);
}
}
當(dāng)然甸鸟,這個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()
鞍匾。