模板方法模式
定义
模板方法模式(Template Method Pattern)是指定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤,属于行为型设计模式。
模板方法模式实际上是封装了一个固定流程,该流程由几个步骤组成,具体步骤可以由子类进行不同实现,从而让固定的流程产生不同的结果。它非常简单,其实就是类的继承机制,但它却是一个应用非常广泛的模式。模板方法模式的本质是抽象封装流程,具体进行实现。
应用场景
在我们日常生活中,模板方法模式也很常见。比如我们平时办理入职流程填写入职登记表->打印简历->复印学历->复印身份证->签订劳动合同->建立花名册->办理工牌->安排工位等,其他还有很多。
模板方法模式适用于以下场景:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;
- 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。
模板方法模式主要包含两种角色:
- 抽象模板(AbstractClass):抽象模板类,定义一套算法框架/流程;
- 具体实现(ConcreteClass):具体实现类,对算法框架/流程的某些步骤进行了实现;
利用模板方法模式重构 JDBC 操作业务场景
我们不使用 spring 提供的 JDBCTemplate 来自己实现一个,首先创建自己的 JDBCTemplate 类,封装JDBC 操作。以查询为例,每次查询的表不同,返回的数据结构也就不一样。我们针对不同的数据,都要封装成不同的实体对象。而每个实体封装的逻辑都是不一样,但封装前和封装后的流程是一样的,因此,我们可以使用模板方法模式来设计这样的业务场景。先创建约束 ORM逻辑的接口RowMapper:
import java.sql.ResultSet;
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws Exception;
}
再创建封装了所有处理流程的抽象类 JDBCTemplate:
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public abstract class JdbcTemplate {
private DataSource dataSource;
public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
public final List<?> executeQuery(String sql,RowMapper<?> rowMapper,Object[] values){
try {
//1、获取连接
Connection conn = this.getConnection();
//2、创建语句集
PreparedStatement pstm = this.createPrepareStatement(conn,sql);
//3、执行语句集
ResultSet rs = this.executeQuery(pstm,values);
//4、处理结果集
List<?> result = this.parseResultSet(rs,rowMapper);
//5、关闭结果集
rs.close();
//6、关闭语句集
pstm.close();
//7、关闭连接
conn.close();
return result;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
private List<?> parseResultSet(ResultSet rs, RowMapper<?> rowMapper) throws Exception {
List<Object> result = new ArrayList<Object>();
int rowNum = 0;
while (rs.next()){
result.add(rowMapper.mapRow(rs,rowNum++));
}
return result;
}
private ResultSet executeQuery(PreparedStatement pstm, Object[] values) throws SQLException {
for (int i = 0; i < values.length; i++) {
pstm.setObject(i,values[i]);
}
return pstm.executeQuery();
}
private PreparedStatement createPrepareStatement(Connection conn, String sql) throws SQLException {
return conn.prepareStatement(sql);
}
private Connection getConnection() throws SQLException {
return this.dataSource.getConnection();
}
}
创建实体对象 Member类:
@Data
public class Member {
private String username;
private String password;
private String nickname;
private int age;
private String addr;
}
创建数据库操作类 MemberDao:
public class MemberDao extends JdbcTemplate {
public MemberDao(DataSource dataSource) {
super(dataSource);
}
public List<?> selectAll(){
String sql = "select * from t_member";
return super.executeQuery(sql, (RowMapper<Member>) (rs, rowNum) -> {
Member member = new Member();
//字段过多,原型模式
member.setUsername(rs.getString("username"));
member.setPassword(rs.getString("password"));
member.setAge(rs.getInt("age"));
member.setAddr(rs.getString("addr"));
return member;
},null);
}
}
创建测试类:
public static void main(String[] args) {
//实际开发中不会这样写,这里是为了演示才这么写的
MemberDao memberDao = new MemberDao(null);
List<?> result = memberDao.selectAll();
System.out.println(result);
}
模板方法模式在源码中的体现
在 JDK 中,每天都在使用的HttpServlet,有三个方法service()、doGet()、doPost()都是模板方法的抽象类,另外还有AbstractList:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
// 省略其余代码...
abstract public E get(int index);
}
在 mybatis 中也有一些经典的应用,来看一下BaseExecutor类,它是一个基础的 SQL执行类,实现了大部分的SQL 执行逻辑,然后把几个方法交给子类定制化完成,源码如下:
public abstract class BaseExecutor implements Executor {
protected abstract int doUpdate(MappedStatement var1, Object var2) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean var1) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4) throws SQLException;
}
这几个方法交由子类来实现,再来看下一下 BaseExecutor 的子类都是怎么做的:
看一下SimpleExecutor的 doUpdate():
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
int var6;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var6 = handler.update(stmt);
} finally {
this.closeStatement(stmt);
}
return var6;
}
//省略...
}
再来看一下 BatchExecutor 的doUpdate() :
public class BatchExecutor extends BaseExecutor {
//省略...
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
Statement stmt;
if (sql.equals(this.currentSql) && ms.equals(this.currentStatement)) {
int last = this.statementList.size() - 1;
stmt = (Statement)this.statementList.get(last);
this.applyTransactionTimeout(stmt);
handler.parameterize(stmt);
BatchResult batchResult = (BatchResult)this.batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = this.getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
this.currentSql = sql;
this.currentStatement = ms;
this.statementList.add(stmt);
this.batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
handler.batch(stmt);
return -2147482646;
}
//省略...
}
很形象的体现出了模板方法的用处。
模板方法模式的优缺点
优点:
- 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性;
- 将不同的代码放到不同的子类中,通过对子类的扩展增加新的行为,提高代码的扩展性;
- 不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。
缺点:
- 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加;
- 类数量的增加,同接地增加了系统实现的复杂度;
- 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。