模板方法模式是類的行為模式。準(zhǔn)備一個(gè)抽象類,將部分邏輯以具體方法以及具體構(gòu)造函數(shù)的形式體現(xiàn),然后聲明一些抽象方法來迫使子類實(shí)現(xiàn)剩余的邏輯,不同的子類可以有不同的方式實(shí)現(xiàn)這些抽象方法,從而對(duì)剩余的邏輯有不同的實(shí)現(xiàn)。這就是模板方法模式的用意。
模板方法模式基于繼承的代碼復(fù)用。
模板方法模式需要開發(fā)抽象類和具體子類設(shè)計(jì)師之間的協(xié)作。一個(gè)設(shè)計(jì)師負(fù)責(zé)給出一個(gè)算法的輪廓和骨架;另一個(gè)設(shè)計(jì)師負(fù)責(zé)給出這個(gè)算法的各個(gè)邏輯步驟。代表這些具體邏輯步驟的方法稱為基本方法(primitive method),而將這些基本方法匯總起來的方法叫做模板方法(template method),這個(gè)設(shè)計(jì)模式的名字就是從此而來的。
1. 模板方法的結(jié)構(gòu)

模板方法所代表的行為稱為頂級(jí)行為,其邏輯稱為頂級(jí)邏輯。這里設(shè)計(jì)到兩個(gè)角色:
-
抽象模板(Abstract Template)角色
定義一個(gè)或多個(gè)抽象行為,以便讓子類實(shí)現(xiàn),這些抽象操作叫做基本操作,它們是一個(gè)頂級(jí)邏輯的組成步驟。
定義并實(shí)現(xiàn)了一個(gè)模板方法。這個(gè)模板方法一般是一個(gè)具體方法,他給出了一個(gè)頂級(jí)邏輯的骨架,而邏輯的組成步驟在相應(yīng)的抽象操作中,推遲到子類實(shí)現(xiàn),頂級(jí)邏輯也有可能調(diào)用一些具體方法。
-
具體模板(Concrete Template)角色
實(shí)現(xiàn)父類所定義的一個(gè)或多個(gè)抽象方法,它們是一個(gè)頂級(jí)邏輯的組成步驟。
每一個(gè)抽象模板角色都可以有任意多個(gè)具體模板角色與之對(duì)應(yīng),而每一個(gè)具體模板角色都可以給出這些抽象方法(也就是頂級(jí)邏輯的組成步驟)的不同實(shí)現(xiàn),從而使得頂級(jí)邏輯的實(shí)現(xiàn)各不相同。
2. 模板方法的代碼實(shí)現(xiàn)
模板方法類,abstractMethod()、hookMethod()等基本方法是頂級(jí)邏輯(templateMethod())的組成步驟。
//抽象模板角色
public abstract class AbstractTemplate {
//模板方法的頂級(jí)邏輯
public void templateMethod() {
abstractMethod();
hookMethod();
concreteMethod();
}
//基本方法(強(qiáng)制由子類實(shí)現(xiàn))
protected abstract void abstractMethod();
//基本方法(可選擇由子類實(shí)現(xiàn))
protected void hookMethod(){}
//公共方法(父類實(shí)現(xiàn))
private final void concreteMethod(){
//業(yè)務(wù)相關(guān)代碼
}
}
具體模板角色類,實(shí)現(xiàn)了父類所聲明的基本方法,abstractMethod()方法所代表的就是強(qiáng)制子類實(shí)現(xiàn)的剩余邏輯,而hookMethod()方法是可選擇實(shí)現(xiàn)的邏輯,不是必須實(shí)現(xiàn)的。
//具體模板角色
public class ConcreteTemplate extends AbstractTemplate {
//強(qiáng)制重寫父類的抽象方法
@Override
protected void abstractMethod() {
//業(yè)務(wù)相關(guān)代碼
}
//選擇重寫父類的具體方法
@Override
protected void hookMethod() {
//業(yè)務(wù)相關(guān)代碼
}
}
模板方法的關(guān)鍵是:子類可以置換父類的可變部分,但是子類卻不可以改變模板方法所代表的頂級(jí)邏輯。
每當(dāng)定義一個(gè)新的子類時(shí),不要按照控制流程的思路去想,而應(yīng)該按照“責(zé)任”的思路去想。換言之,應(yīng)當(dāng)考慮哪些操作是必須置換掉的,哪些操作是可以置換掉的,以及哪些操作是不可以置換掉的。使用模板方法可以使這些邏輯變得清晰。
3. 模板方法模式中的方法
模板方法模式可以分為兩大類:模板方法和基本方法。
3.1 模板方法
一個(gè)模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個(gè)總算法或一個(gè)總行為的方法。
一個(gè)抽象類可以有任意多個(gè)模板方法,而不限于一個(gè)。每個(gè)模板方法都可以調(diào)用任意多個(gè)具體方法。
3.2 基本方法
基本方法又可以分為三種:抽象方法(Abstract Method)、具體方法(Concrete Method)、鉤子方法(Hook Method)。
抽象方法:一個(gè)抽象方法由抽象類聲明,由具體子類實(shí)現(xiàn)。在Java語(yǔ)言中抽象方法使用abstract關(guān)鍵字。
具體方法:一個(gè)具體方法由抽象類聲明實(shí)現(xiàn),而子類并不實(shí)現(xiàn)或置換。
鉤子方法:一個(gè)鉤子方法由抽象類聲明并實(shí)現(xiàn),而子類會(huì)加以擴(kuò)展,通常抽象類給出的實(shí)現(xiàn)是一個(gè)空實(shí)現(xiàn),作為方法的默認(rèn)實(shí)現(xiàn)。
3.3 默認(rèn)鉤子方法
一個(gè)鉤子方法常常由抽象類給出一個(gè)空實(shí)現(xiàn)作為此方法的默認(rèn)實(shí)現(xiàn)。這個(gè)空的鉤子方法叫做“Do Nothing Hook”,顯然,這種默認(rèn)鉤子方法在缺省適配模式里面已經(jīng)見過了,一個(gè)缺省適配模式將的是一個(gè)類為一個(gè)接口提供一個(gè)默認(rèn)的空實(shí)現(xiàn),從而使得缺省適配類的子類不必像實(shí)現(xiàn)接口那個(gè)必須給出所有方法的實(shí)現(xiàn),因?yàn)橥ǔR粋€(gè)具體類并不需要所有的方法。
3.4 鉤子方法的命名規(guī)則
鉤子方法的名字應(yīng)當(dāng)以do開始,這是熟悉設(shè)計(jì)模式的java開發(fā)人員的標(biāo)準(zhǔn)做法。在上面的例子中,鉤子方法hookMethod()應(yīng)當(dāng)以do開頭,在httpServlet類中,也遵從這一命名規(guī)則,如doGet()、doPost()等方法。
4. 模板設(shè)計(jì)模式的優(yōu)缺點(diǎn)
4.1 優(yōu)點(diǎn)
-
提高代碼的復(fù)用性。
將相同部分的代碼放在抽象的父類中。 -
提高了擴(kuò)展性。
將不同的代碼放到了不同的子類中,通過子類擴(kuò)展新的行為。 -
實(shí)現(xiàn)了開放封閉原則。
通過一個(gè)父類調(diào)用其子類的操作,通過對(duì)子類的擴(kuò)展增加新的行為,實(shí)現(xiàn)了“開放-封閉”原則。
4.2 缺點(diǎn)
引入了抽象類,每個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類來實(shí)現(xiàn),導(dǎo)致類的個(gè)數(shù)增加,增加了系統(tǒng)實(shí)現(xiàn)的復(fù)雜性。
5. 應(yīng)用場(chǎng)景
- 一次性實(shí)現(xiàn)一個(gè)算法不變的部分,并將可變的行為留給子類來實(shí)現(xiàn)。
- 各子類中公共的行為應(yīng)被提取出來并集中到一個(gè)公共父類中以避免代碼的重復(fù)。
- 控制子類的擴(kuò)展。
5.1 銀行利息計(jì)算
考慮一個(gè)計(jì)算存款利息的例子。假設(shè)系統(tǒng)需要支持兩種存款賬號(hào),即貨幣市場(chǎng)(Money Market)賬號(hào)和定期存款(Certificate of Deposite)賬號(hào)。這兩種賬號(hào)的存款利息是不同的,因此,在計(jì)算一個(gè)存戶的存款利息額時(shí),必須區(qū)分兩種不同的賬號(hào)類型。
這個(gè)系統(tǒng)的總行為應(yīng)當(dāng)是計(jì)算出利息,那么就決定了作為一個(gè)模板方法模式的頂級(jí)邏輯應(yīng)當(dāng)是利息計(jì)算。利息計(jì)算涉及到兩個(gè)步驟:一是基本方法給出賬戶種類,另一個(gè)基本方法給出利息的百分比。這兩個(gè)基本方法構(gòu)成具體邏輯,因?yàn)橘~號(hào)的類型不同,所以具體邏輯會(huì)有所不同。

抽象模板角色
//抽象模板角色
public abstract class Account {
//模板方法
public final double calulateInterest() {
//計(jì)算利率
double interestRate = doCalulateInterestRate();
String accountType = doCalulateAccountType();
//計(jì)算賬戶余額
double amount = calulateAmount(accountType);
//返回利息
return amount * interestRate;
}
//查看卡片信息
protected abstract String doCalulateAccountType();
//計(jì)算利率
protected abstract double doCalulateInterestRate();
//基本方法,已經(jīng)實(shí)現(xiàn)
private double calulateAmount(String accountType) {
//省略邏輯代碼
return 100.00;
}
}
具體子類角色
public class MoneyMarketAccount extends Account{
@Override
protected String doCalulateAccountType() {
return "Money Market";
}
@Override
protected double doCalulateInterestRate() {
return 0.045;
}
}
客戶端
public class AccountClient {
public static void main(String[] args) {
Account account=new MoneyMarketAccount();
double interest = account.calulateInterest();
System.out.println("現(xiàn)金賬戶的利息是"+interest+"元");
}
}
5.2 模板方法在Servlet中的應(yīng)用
使用Servlet需要繼承一個(gè)HttpServlet的抽象類,HttpServlet類提供了一個(gè)service()方法,這個(gè)方法調(diào)用七個(gè)doXXX()方法中的一個(gè)或多個(gè),完成對(duì)客戶端調(diào)用的響應(yīng)。這些doXXX()方法需要由HttpServlet的具體子類提供,因此是典型的模板方法模式。
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);
}
}

我們可以通過重寫父類的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中七個(gè)基本方法中的其中兩個(gè),分別是doGet()和doPost()。
參考實(shí)現(xiàn)