## 引入
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述模板方法(Template Method)模式的:
> 模板方法模式是類的行為模式。準備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,然后聲明一些抽象方法來迫使子類實現剩余的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩余的邏輯有不同的實現。這就是模板方法模式的用意。
## 定義
所謂模板方法模式就是在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。
模板方法模式是基于繼承的代碼復用技術的。在模板方法模式中,我們可以將相同部分的代碼放在父類中,而將不同的代碼放入不同的子類中。也就是說我們需要聲明一個抽象的父類,將部分邏輯以具體方法以及具體構造函數的形式實現,然后聲明一些抽象方法讓子類來實現剩余的邏輯,不同的子類可以以不同的方式來實現這些邏輯。
其實所謂模板就是一個方法,這個方法將算法的實現定義成了一組步驟,其中任何步驟都是可以抽象的,交由子類來負責實現。這樣就可以保證算法的結構保持不變,同時由子類提供部分實現。
模板是一個方法,那么他與普通的方法存在什么不同呢?模板方法是定義在抽象類中,把基本操作方法組合在一起形成一個總算法或者一組步驟的方法。而普通的方法是實現各個步驟的方法,我們可以認為普通方法是模板方法的一個組成部分。
## 結構
模板方法模式是所有模式中最為常見的幾個模式之一,是基于繼承的代碼復用的基本技術。
模板方法模式需要開發抽象類和具體子類的設計師之間的協作。一個設計師負責給出一個算法的輪廓和骨架,另一些設計師則負責給出這個算法的各個邏輯步驟。代表這些具體邏輯步驟的方法稱做基本方法(primitive method);而將這些基本方法匯總起來的方法叫做模板方法(template method),這個設計模式的名字就是從此而來。
模板方法所代表的行為稱為頂級行為,其邏輯稱為頂級邏輯。模板方法模式的靜態結構圖如下所示:

這里涉及到兩個角色:
抽象模板(Abstract Template)角色有如下責任:
* 定義了一個或多個抽象操作,以便讓子類實現。這些抽象操作叫做基本操作,它們是一個頂級邏輯的組成步驟。
* 定義并實現了一個模板方法。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能調用一些具體方法。
具體模板(Concrete Template)角色又如下責任:
* 實現父類所定義的一個或多個抽象方法,它們是一個頂級邏輯的組成步驟。
* 每一個抽象模板角色都可以有任意多個具體模板角色與之對應,而每一個具體模板角色都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同。
## 代碼實現
舉個例子,以準備去學校所要做的工作(prepareGotoSchool)為例,假設需要分三步:穿衣服(dressUp),吃早飯(eatBreakfast),帶上東西(takeThings)。學生和老師要做得具體事情肯定有所區別。
抽象類AbstractClass
~~~
public abstract class AbstractPerson{
//抽象類定義整個流程骨架
public void prepareGotoSchool(){
dressUp();
eatBreakfast();
takeThings();
}
//以下是不同子類根據自身特性完成的具體步驟
protected abstract void dressUp();
protected abstract void eatBreakfast();
protected abstract void takeThings();
}
~~~
具體類ConcreteClass
~~~
public class Student extends AbstractPerson{
@Override
protected void dressUp() {
System.out.println(“穿校服");
}
@Override
protected void eatBreakfast() {
System.out.println(“吃媽媽做好的早飯");
}
@Override
protected void takeThings() {
System.out.println(“背書包,帶上家庭作業和紅領巾");
}
}
~~~
~~~
public class Teacher extends AbstractPerson{
@Override
protected void dressUp() {
System.out.println(“穿工作服");
}
@Override
protected void eatBreakfast() {
System.out.println(“做早飯,照顧孩子吃早飯");
}
@Override
protected void takeThings() {
System.out.println(“帶上昨晚準備的考卷");
}
}
~~~
客戶端
~~~
public class Client {
public static void main(String[] args) {
Student student = new Student()
student.prepareGotoSchool();
Teacher teacher = new Teacher()
teacher.prepareGotoSchool();
}
}
~~~
模板模式的關鍵是:子類可以置換掉父類的可變部分,但是子類卻不可以改變模板方法所代表的頂級邏輯。
每當定義一個新的子類時,不要按照控制流程的思路去想,而應當按照“責任”的思路去想。換言之,應當考慮哪些操作是必須置換掉的,哪些操作是可以置換掉的,以及哪些操作是不可以置換掉的。使用模板模式可以使這些責任變得清晰。
**引入鉤子方法**
針對上面的情景,如果有人不吃早飯,但上述算法卻總會有吃早飯的操作,遇到這個問題我們可以使用鉤子。所謂鉤子就是一種被聲明在抽象類中的方法,但只有空的或者默認的實現。鉤子的存在可以使子類能夠對算法的不同點進行掛鉤,即讓子類能夠對模板方法中某些即將發生變化的步驟做出相應的反應。當然要不要掛鉤,由子類決定。
代碼修改為:
~~~
public abstract class AbstractPerson{
//抽象類定義整個流程骨架
public void prepareGotoSchool(){
dressUp();
eatBreakfast();
takeThings();
}
//以下是不同子類根據自身特性完成的具體步驟
protected abstract void dressUp();
//鉤子方法
protected abstract void doEatBreakfast(){
}
protected abstract void takeThings();
}
~~~
## 模板方法模式中的方法
模板方法中的方法可以分為兩大類:模板方法和基本方法。
**模板方法**
一個模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個總算法或一個總行為的方法。
一個抽象類可以有任意多個模板方法,而不限于一個。每一個模板方法都可以調用任意多個具體方法。
**基本方法**
基本方法又可以分為三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。
* 抽象方法:一個抽象方法由抽象類聲明,由具體子類實現。在Java語言里抽象方法以abstract關鍵字標示。
* 具體方法:一個具體方法由抽象類聲明并實現,而子類并不實現或置換。
* 鉤子方法:一個鉤子方法由抽象類聲明并實現,而子類會加以擴展。通常抽象類給出的實現是一個空實現,作為方法的默認實現。
**默認鉤子方法**
一個鉤子方法常常由抽象類給出一個空實現作為此方法的默認實現。這種空的鉤子方法叫做“Do Nothing Hook”。顯然,這種默認鉤子方法在缺省適配模式里面已經見過了,一個缺省適配模式講的是一個類為一個接口提供一個默認的空實現,從而使得缺省適配類的子類不必像實現接口那樣必須給出所有方法的實現,因為通常一個具體類并不需要所有的方法。
**命名規則**
命名規則是設計師之間賴以溝通的管道之一,使用恰當的命名規則可以幫助不同設計師之間的溝通。
鉤子方法的名字應當以do開始,這是熟悉設計模式的Java開發人員的標準做法。在上面的例子中,鉤子方法hookMethod()應當以do開頭;在HttpServlet類中,也遵從這一命名規則,如doGet()、doPost()等方法。
## 模板方法模式在Servlet中的應用
使用過Servlet的人都清楚,除了要在web.xml做相應的配置外,還需繼承一個叫HttpServlet的抽象類。HttpService類提供了一個service()方法,這個方法調用七個do方法中的一個或幾個,完成對客戶端調用的響應。這些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擔任**抽象模板**角色
模板方法:由service()方法擔任。
基本方法:由doPost()、doGet()等方法擔任。
TestServlet擔任**具體模板**角色
TestServlet置換掉了父類HttpServlet中七個基本方法中的其中兩個,分別是doGet()和doPost()。
## 優點
* 1、模板方法模式在定義了一組算法,將具體的實現交由子類負責。
* 2、模板方法模式是一種代碼復用的基本技術。
* 3、模板方法模式導致一種反向的控制結構,通過一個父類調用其子類的操作,通過對子類的擴展增加新的行為,符合“開閉原則”。
## 缺點
* 每一個不同的實現都需要一個子類來實現,導致類的個數增加,是的系統更加龐大。
## 使用場景
* 1、 一次性實現一個算法的不變的部分,并將可變的行為留給子類來實現。
* 2、 各子類中公共的行為應被提取出來并集中到一個公共父類中以避免代碼重復。
* 3、控制子類的擴展。
## 總結
* 1、 模板方法模式定義了算法的步驟,將這些步驟的實現延遲到了子類。
* 2、 模板方法模式為我們提供了一種代碼復用的重要技巧。
* 3、 模板方法模式的抽象類可以定義抽象方法、具體方法和鉤子。
* 4、 為了防止子類改變算法的實現步驟,我們可以將模板方法聲明為final。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux