Template Pattern
認(rèn)識(shí)模板方法
在一個(gè)方法中定義一個(gè)算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
案例
【Head First】案例UML圖

【Head First】案例代碼
關(guān)于代碼的注釋和說明,放在對(duì)應(yīng)的代碼里面更方便理解。
- CaffeineBeverage.java
/**
* 咖啡因飲料
*
*/
public abstract class CaffeineBeverage {
/**
* 定義制作咖啡因飲料的算法步驟:把水煮沸 -> 沖泡 -> 把飲料倒進(jìn)杯子 -> 加調(diào)料 算法的每一個(gè)步驟都被一個(gè)方法代表了。
* brew()和addCondiments()方法需要子類來提供實(shí)現(xiàn)。
*/
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
/**
* 沖泡
*/
abstract void brew();
/**
* 加調(diào)料
*/
abstract void addCondiments();
void boilWater() {
System.out.println("把水煮沸");
}
void pourInCup() {
System.out.println("把飲料倒進(jìn)杯子");
}
/**
* 鉤子方法 可以讓子類有能力對(duì)算法的 不同點(diǎn)進(jìn)行掛鉤。
* 鉤子的目的:子類實(shí)現(xiàn)算法的可選的部分;子類能夠有機(jī)會(huì)對(duì)模板方法中的某些即將發(fā)生的步驟做出反應(yīng)。
* 通過鉤子的控制,子類可以控制是否執(zhí)行算法里面的某個(gè)步驟, 例如飲料是否要加調(diào)料,飲料是否需要配送
*/
boolean customerWantsCondiments() {
return true;
}
}
- Tea.java
public class Tea extends CaffeineBeverage {
/**
* 茶在這里作為子類的角色,提供了沖泡步驟的實(shí)現(xiàn)
*/
@Override
void brew() {
System.out.println("沖泡茶");
}
/**
* 茶在這里作為子類的角色,提供了添加調(diào)料步驟的實(shí)現(xiàn)
*/
@Override
void addCondiments() {
System.out.println("添加檸檬調(diào)料");
}
}
- Coffe.java
public class Coffee extends CaffeineBeverage {
/**
* 咖啡在這里作為子類的角色,提供了沖泡步驟的實(shí)現(xiàn)
*/
@Override
void brew() {
System.out.println("沖泡咖啡");
}
/**
* 咖啡在這里作為子類的角色,提供了添加調(diào)料步驟的實(shí)現(xiàn)
*/
@Override
void addCondiments() {
System.out.println("添加糖和牛奶");
}
}
JDK AbstractCollection的模板方法
- AbstractCollection.java
public abstract class AbstractCollection<E> implements Collection<E> {
...
// 添加單個(gè)元素的算法延遲到了子類實(shí)現(xiàn)
public boolean add(E e) {
throw new UnsupportedOperationException();
}
// 添加一個(gè)集合的算法步驟
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
...
}
- ArrayList.java
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
...
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
...
}
- HashSet.java
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
...
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
...
}
【Spring】JdbcTemplate的UML圖

【Spring】JdbcTemplate主要代碼
直接使用JDBC驅(qū)動(dòng)來編寫數(shù)據(jù)庫訪問代碼,算法步驟如下:
1. 注冊(cè)并加載JDBC驅(qū)動(dòng)程序
2. 建立與數(shù)據(jù)庫的連接
3. 創(chuàng)建Statement對(duì)象
4. 執(zhí)行sql語句
5. 處理sql結(jié)果集
6. 異常處理
7. 釋放資源
對(duì)于數(shù)據(jù)庫的每次訪問,都要經(jīng)過上面7個(gè)步驟,寫一兩次問題不大,現(xiàn)在的程序邏輯一般都要和DB做交互,每次交互都走遍7個(gè)步驟,關(guān)鍵是大部分代碼都是相同的。
要解決這個(gè)問題,運(yùn)用面向?qū)ο蟮脑瓌t,區(qū)分不變的步驟和變化的步驟。上面7個(gè)步驟里面只有第4個(gè)和第5個(gè)步驟是會(huì)根據(jù)業(yè)務(wù)場(chǎng)景變化的。
Spring將數(shù)據(jù)訪問過程中不變的和變化的部分明確劃分為兩個(gè)不同的類:模板(template)和回調(diào)(callback)。模板類管理數(shù)據(jù)訪問過程中不變的部分,例如事務(wù)控制,資源管理和異常處理?;卣{(diào)類處理自定義的數(shù)據(jù)訪問代碼,例如SQL語句,綁定參數(shù)和處理結(jié)果集。
+------------------------+ +-----------------------+
| | | |
| 模 板 類 | | 回 調(diào) 類 |
| | | |
| +------------------+ | | +------------------+ |
| | | | | | | |
| | | | | | | |
| | 1、準(zhǔn) 備 資 源 | | | | | |
| | +--------------> 3、執(zhí) 行 事 務(wù) | |
| | 2、開 始 事 務(wù) | | | | | |
| | | | | | | |
| | | | | | | |
| +------------------+ | | +------------------+ |
| | | |
| +------------------+ | | +------------------+ |
| | | | | | | |
| | | | | | | |
| | 5、提 交 / 回 滾 | | | | | |
| | <--------------+ 4、返 回 數(shù) 據(jù) | |
| | 6、關(guān) 閉 資 源 | | | | | |
| | | | | | | |
| | | | | | | |
| +------------------+ | | +------------------+ |
| | | |
+------------------------+ +-----------------------+
- JdbcTemplate.java
/**
* 模板方法的設(shè)計(jì)模式一般是通過繼承來實(shí)現(xiàn),但是這里并沒有采用繼承讓子類承擔(dān)某個(gè)算法的步驟,而是采用了組合委托給另外一個(gè)類來負(fù)責(zé)某個(gè)算法的步驟。
* 而組合的方式也不是在JdbcTemplate類中聲明固定的變量名,而是通過傳參的方式來實(shí)現(xiàn)。
*
* queryXXX()和updateXXX()方法最終都轉(zhuǎn)到executeXXX()方法。
* execute()方法就是Template Pattern里面的Template Method,定義了訪問數(shù)據(jù)庫的算法骨架:獲取數(shù)據(jù)庫連接 -> 創(chuàng)建Statement對(duì)象 -> 執(zhí)行SQL -> 捕獲異常 -> 釋放資源
* 對(duì)于【執(zhí)行SQL】這個(gè)算法步驟,JdbcTemplate委托給StatementCallback對(duì)象action來處理。
* 程序員就可以只關(guān)注自己的代碼邏輯,而不必關(guān)注管理資源和異常處理等問題。
*
*/
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
- StatementCallback子類
StatementCallback子類都是定義在JdbcTemplage.execute()方法里面的內(nèi)部類,共有4個(gè):QueryStatementCallback,UpdateStatementCallback,ExecuteStatementCallback,BatchUpdateStatementCallback。
下來列舉其中兩個(gè)類的定義:
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
// 查詢類的執(zhí)行需要處理結(jié)果集
rs = stmt.executeQuery(sql);
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
return rse.extractData(rsToUse);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
public String getSql() {
return sql;
}
}
class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
public Integer doInStatement(Statement stmt) throws SQLException {
// 更新類的可以獲取到更新的行數(shù)
int rows = stmt.executeUpdate(sql);
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows");
}
return rows;
}
public String getSql() {
return sql;
}
}
總結(jié)
適用范圍
- 「模板方法」定義了算法的步驟,把某些步驟的實(shí)現(xiàn)延遲到子類
- 模板方法模式為我們提供了一種代碼復(fù)用的重要技巧
- 模板方法的抽象類可以定義具體方法、抽象方法和鉤子
- 抽象方法由子類實(shí)現(xiàn)
- 鉤子是一個(gè)方法,它在抽象類中不做事,或者只做默認(rèn)的事情,子類可以選擇要不要覆蓋它
- 為了防止子類改變模板方法中的算法,可以將模板方法聲明為final
- 模板方法的實(shí)踐應(yīng)用中有可能會(huì)有很多變化
- 工廠方法是模板方法的一種特殊版本
與策略模式的比較
- 兩個(gè)模式都是封裝算法,一個(gè)用組合,一個(gè)用繼承。
體現(xiàn)的OO原則
Don't call me, I will call you.
好萊塢原則:將決策權(quán)放在高層模塊中,以便決定如何以及何時(shí)調(diào)用底層模塊。