模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。它适应的场景有:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。(百度百科),
看到定义或许会很迷糊,我们可以通过例子说明,其实模板模式的一个典型的应用就是Spring的JdbcTemplate,这是一个经典的模板模式,我们就以Spring的JdbcTemplate为例子介绍模板模式,在介绍JdbcTemplate之前我们可以先想一下Java对数据库的操作包括哪几部分?数据库的操作可以简单的分为以下几部分:数据源的配置,数据库连接的获取,SQL的执行,结果集的处理,这四步只有结果集会有不同的处理,前三个执行逻辑基本上都是一样的。因此我们可以将前三步封装成一个模板即JdbcTemplate,结果集的实现则交给调用者自己实现。我们可以查看JdbcTemplate的源码:
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
......
}
上面的源码中JdbcAccessor是对数据源的管理,JdbcOperations则是对数据库操作接口定义,JdbcTemplate实现了JdbcOperations将调用逻辑封装成一个模板,如下为JdbcAccessor和JdbcOperations部分源码。更多源码可以参考Spring源码。
public abstract class JdbcAccessor implements InitializingBean {
protected final Log logger = LogFactory.getLog(getClass());
private DataSource dataSource;
private SQLExceptionTranslator exceptionTranslator;
private boolean lazyInit = true;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public DataSource getDataSource() {
return this.dataSource;
}
//更多方法参考Spring源码
......
}
public interface JdbcOperations {
.....
<T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException;
<T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException;
//更多定义查看Spring源码
......
}
接下来就是模板方法的封装,我们以上面的List<T> query(String sql, RowMapper<T> rowMapper)方法为例,查看是怎么封装模板方法的。
public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {
//调用下面的query方法,
return query(sql, new RowMapperResultSetExtractor<T>(rowMapper));
}
//该方法也没有执行数据库操作,而是交给execute(StatementCallback<T> action)方法
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
//执行数据库操作
rs = stmt.executeQuery(sql);
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
//将查询的结果集交给RowMapperResultSetExtractor实例处理
return rse.extractData(rsToUse);
}finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
//真正查询数据库的方法
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);
}
//创建Statement
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
//执行SQL语句调用的是上面方法的内部类,QueryStatementCallback
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}catch (SQLException ex) {
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());
}
}
阅读上面的源码,最终结果集的处理交给了ResultSetExtractor的extractData方法,我们再看该方法的实现:
@Override
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>());
int rowNum = 0;
while (rs.next()) {
//最终调用RowMapper接口的mapRow方法实现数据的转换,RowMapper需要我们自己实现
results.add(this.rowMapper.mapRow(rs, rowNum++));
}
return results;
}
上面方法使用RowMapper的mapRow实现结果集与对象之间的转换,如下我们查询一个一个消息记录的例子:
jdbcTemplate.query("select * from sms", new RowMapper<Sms>(){
@Override
public Sms mapRow(ResultSet rs, int rowNum) throws SQLException {
//自己实现之后返回要查询的对象
return null;
}
});
利用模板方法将相同处理逻辑的代码放到抽象父类中,以提高代码的复用性。将不同的代码使用不同的子类实现,通过对子类的扩展增加新的行为,提高代码的扩展性。很好的实现了开闭原则。但是随着不同代码的增多,需要每一个抽象类都需要一个子类来实现,这样导致类的个数增加。增加了系统的复杂性。