1、上下文及问题
某些操作,有一系列的固定步骤,其中只有少部分操作是变化了,没有模式的情况下,每次都需要重复大部分的固定步骤,重复工作多,另外如果这些大部分的操作是有顺序或规范的话,容易写错
2、常见的场景
(1)jdbc操作,如Spring JDBC Template
(2)分页查询数据文件下载写入
3、解决方式
引入模板,将通用操作步骤固定起来,抽象出变化的部分
(1)抽象继承方式:让子类去重写个性化的内容
(2)组合回调方式:Java采用匿名类,Scala采用函数参数
4、模式抽象图
templateMethod()里头封装了一些模板方法,然后将个性化的部分,以subMethod方式给以子类重写,达到个性化目的。
但通常,不大使用这种继承的实现方法,一般采用匿名接口/函数传递的模式
5、代码示例
(1)Spring JDBC Template
public Book findById2(Integer id){ return jdbcTemplate.query("select * from book where book_id=?",new Object[]{id},new RowMapper() { @Override public Book mapRow(ResultSet rs, int rowNum) throws SQLException { Book book = new Book(); book.setBookId(rs.getInt("book_id")); book.setTitle(rs.getString("title")); book.setCreatedAt(rs.getTimestamp("created_at")); return book; } }).get(0); }
接口
/** * Callback interface used by {@link JdbcTemplate}'s query methods. * Implementations of this interface perform the actual work of extracting * results from a {@link java.sql.ResultSet}, but don't need to worry * about exception handling. {@link java.sql.SQLException SQLExceptions} * will be caught and handled by the calling JdbcTemplate. * *This interface is mainly used within the JDBC framework itself. * A {@link RowMapper} is usually a simpler choice for ResultSet processing, * mapping one result object per row instead of one result object for * the entire ResultSet. * *
Note: In contrast to a {@link RowCallbackHandler}, a ResultSetExtractor * object is typically stateless and thus reusable, as long as it doesn't * access stateful resources (such as output streams when streaming LOB * contents) or keep result state within the object. * * @author Rod Johnson * @author Juergen Hoeller * @since April 24, 2003 * @see JdbcTemplate * @see RowCallbackHandler * @see RowMapper * @see org.springframework.jdbc.core.support.AbstractLobStreamingResultSetExtractor */public interface ResultSetExtractor
{ /** * Implementations must implement this method to process the entire ResultSet. * @param rs ResultSet to extract data from. Implementations should * not close this: it will be closed by the calling JdbcTemplate. * @return an arbitrary result object, or {@code null} if none * (the extractor will typically be stateful in the latter case). * @throws SQLException if a SQLException is encountered getting column * values or navigating (that is, there's no need to catch SQLException) * @throws DataAccessException in case of custom exceptions */ T extractData(ResultSet rs) throws SQLException, DataAccessException;}
接口实现
/** * Adapter implementation of the ResultSetExtractor interface that delegates * to a RowMapper which is supposed to create an object for each row. * Each object is added to the results List of this ResultSetExtractor. * *Useful for the typical case of one object per row in the database table. * The number of entries in the results list will match the number of rows. * *
Note that a RowMapper object is typically stateless and thus reusable; * just the RowMapperResultSetExtractor adapter is stateful. * *
A usage example with JdbcTemplate: * *
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); // reusable object * RowMapper rowMapper = new UserRowMapper(); // reusable object * * List allUsers = (List) jdbcTemplate.query( * "select * from user", * new RowMapperResultSetExtractor(rowMapper, 10)); * * User user = (User) jdbcTemplate.queryForObject( * "select * from user where id=?", new Object[] {id}, * new RowMapperResultSetExtractor(rowMapper, 1)); * *Alternatively, consider subclassing MappingSqlQuery from the {@code jdbc.object} * package: Instead of working with separate JdbcTemplate and RowMapper objects, * you can have executable query objects (containing row-mapping logic) there. * * @author Juergen Hoeller * @since 1.0.2 * @see RowMapper * @see JdbcTemplate * @see org.springframework.jdbc.object.MappingSqlQuery */public class RowMapperResultSetExtractor
implements ResultSetExtractor
> { private final RowMapper rowMapper; private final int rowsExpected; /** * Create a new RowMapperResultSetExtractor. * @param rowMapper the RowMapper which creates an object for each row */ public RowMapperResultSetExtractor(RowMapper rowMapper) { this(rowMapper, 0); } /** * Create a new RowMapperResultSetExtractor. * @param rowMapper the RowMapper which creates an object for each row * @param rowsExpected the number of expected rows * (just used for optimized collection handling) */ public RowMapperResultSetExtractor(RowMapper rowMapper, int rowsExpected) { Assert.notNull(rowMapper, "RowMapper is required"); this.rowMapper = rowMapper; this.rowsExpected = rowsExpected; } @Override public List extractData(ResultSet rs) throws SQLException { List results = (this.rowsExpected > 0 ? new ArrayList (this.rowsExpected) : new ArrayList ()); int rowNum = 0; while (rs.next()) { results.add(this.rowMapper.mapRow(rs, rowNum++)); } return results; }}
模板调用
/** * Query using a prepared statement, allowing for a PreparedStatementCreator * and a PreparedStatementSetter. Most other query methods use this method, * but application code will always work with either a creator or a setter. * @param psc Callback handler that can create a PreparedStatement given a * Connection * @param pss object that knows how to set values on the prepared statement. * If this is null, the SQL will be assumed to contain no bind parameters. * @param rse object that will extract results. * @return an arbitrary result object, as returned by the ResultSetExtractor * @throws DataAccessException if there is any problem */ publicT query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); logger.debug("Executing prepared SQL query"); return execute(psc, new PreparedStatementCallback () { @Override public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps); } rs = ps.executeQuery(); ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
(2)Scala的Loan Pattern
package persia.demoobject FileLoan { def main(args: Array[String]): Unit = { import java.io._ /** * Execute Around Method 模式的一个变体就是Loan模式 * 如果想确保非内存资源得到确定性的释放,就可以使用这个模式 * 可以这样认为这种资源密集型的对象是接给你的,用过之后应该立即归还。 */ def writeToFile(fname: String)(codeBlock: PrintWriter => Unit)={ val writer = new PrintWriter(new File(fname)) try{ codeBlock(writer) }finally{ writer.close() } } writeToFile("/home/scipio/output.txt"){ writer => writer write "hello from scala" } }}
其实,Loan Pattern是 Execute Around Method模式的变种。
package persia.demoobject ExecuteAround { /** * 将类的构造函数标记为private,这样就不会在这个类或它的半生对象之外创建出类的实例 * 这样只能以确定的方式使用对象,从而保证了其行为是按照确定的方式自动执行 */ class Resource private(){ println("starting transaction...") private def cleanUp(){ println("ending transaction.")} def op1 = println("operation 1") def op2 = println("operation 2") def op3 = println("operation 3") } object Resource{ def use(codeBlock: Resource => Unit){ val res = new Resource try{ codeBlock(res) }finally{ res.cleanUp() } } } def main(args: Array[String]): Unit = { Resource.use{ res => res.op1 res.op2 res.op3 } }}