介紹
模板方式模式定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。
所以父類模板方法中有兩類方法:
- 共同的方法:所有子類都會(huì)用到的代碼
- 不同的方法:子類要覆蓋的方法,分為兩種:
抽象方法:父類中的是抽象方法,子類必須覆蓋
鉤子方法:父類中是一個(gè)空方法,子類繼承了默認(rèn)也是空的
生活中也有許多模板方法的例子,例如手機(jī)的操作(開機(jī)、開軟件等、關(guān)機(jī))、吃飯(點(diǎn)單、吃東西、買單)、煮菜(生火,煮菜、關(guān)火)等等。
spring 中對(duì) Hibernate 的支持,將一些已經(jīng)定好的方法封裝起來(lái),比如開啟事務(wù)、獲取 Session、關(guān)閉 Session 等。
結(jié)構(gòu)圖
抽象類(AbstractClass): 定義抽象的原語(yǔ)操作(primitive operation) ,具體的子類將重定義它們以實(shí)現(xiàn)一個(gè)算法, 實(shí)現(xiàn)一個(gè)模板方法,定義一個(gè)算法的骨架。該模板方法不僅調(diào)用原語(yǔ)操作,也調(diào)用定義
具體子類 (ConcreteClass): 實(shí)現(xiàn)原語(yǔ)操作以完成算法中與特定子類相關(guān)的步驟。
案例
這篇就拿最原始的jdbc連接來(lái)說事。
最原始的jdbc連接代碼
public class BasicJdbc {
public static void main(String[] args) throws Exception{
//1、加載JDBC驅(qū)動(dòng)程序
Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驅(qū)動(dòng)
//2、創(chuàng)建數(shù)據(jù)庫(kù)的連接
Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root"); //鏈接本地MYSQL
//3、創(chuàng)建一個(gè)Statement
Statement stmt = con.createStatement();
//4、執(zhí)行SQL語(yǔ)句
ResultSet res = stmt.executeQuery("select * from user ");
//5、處理結(jié)果
if (res.next()) {
}
//6、關(guān)閉連接
}
}
上面的代碼中,1、2、3、6代碼都輸固定的,只有4、5中是不固定的。所以可以用模板方法將1、2、3、6的代碼封裝在抽象類中,而4、5兩步交給子類。
抽象類
public abstract class JdbcTemplate {
public Object execute(String sql) throws Exception{
Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驅(qū)動(dòng)
Connection con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "root"); //鏈接本地MYSQL
Statement stmt = con.createStatement();
//執(zhí)行SQL語(yǔ)句
sql += addPage();
ResultSet rs = stmt.executeQuery(sql);
//處理結(jié)果
Object object = handleResultSet(rs);
//關(guān)閉連接
return object;
}
public abstract Object handleResultSet(ResultSet rs);
//類似鉤子方法(可用可不用)
public String addPage() {
return "";
}
}
實(shí)現(xiàn)類
public class UserJdbc extends JdbcTemplate{
@Override
public Object handleResultSet(ResultSet rs) {
//處理結(jié)果集
return null;
}
@Override
public String addPage() {
int pageNumber = 1,pageSize = 5;
return "limit "+pageNumber+","+pageSize;
}
}
當(dāng)然,上面的代碼只是闡述大意而已,并不適用于項(xiàng)目中。接下來(lái)會(huì)根據(jù)spring源碼中的JdbcTemplate進(jìn)行分析。
spring模板方法(JdbcTemplate)
spring中的JdbcTemplate不僅僅使用了模板模式,還使用了回調(diào)模式
JdbcTemplate中的execute方法
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());
}
}
可以看到傳入的參數(shù)是StatementCallback類,然后調(diào)用action.doInStatement(stmtToUse)返回一個(gè)泛型結(jié)果,下面看看StatementCallback的源代碼
public interface StatementCallback<T> {
T doInStatement(Statement stmt) throws SQLException, DataAccessException;
}
可以看到就只有一個(gè)doInStatement方法,現(xiàn)在再看看調(diào)用execute(StatementCallback<T> action)的方法
public void execute(final String sql) throws DataAccessException {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL statement [" + sql + "]");
}
class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
@Override
public Object doInStatement(Statement stmt) throws SQLException {
stmt.execute(sql);
return null;
}
@Override
public String getSql() {
return sql;
}
}
execute(new ExecuteStatementCallback());
}
這里定義了一個(gè)內(nèi)部類ExecuteStatementCallback 實(shí)現(xiàn)了StatementCallback接口,然后在調(diào)用execute(StatementCallback<T> action)方法,最后再回調(diào)ExecuteStatementCallback 內(nèi)部類的doInStatement方法。
這樣做的話可以有效減少子類的個(gè)數(shù),不同的處理結(jié)果只要傳入不同的實(shí)現(xiàn)了StatementCallback接口的類就可以了, 這樣其實(shí)也是Spring的無(wú)侵入設(shè)計(jì)思想的體現(xiàn),同時(shí)也是“依賴優(yōu)于繼承”設(shè)計(jì)理念的體現(xiàn)。