一、模板方法模式是什么?有哪些应用场景?
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。它适应的场景有:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。(百度百科)。
看到定义或许会很迷糊,我们可以通过例子说明,其实模板模式的一个典型的应用就是Spring的JdbcTemplate,这是一个经典的模板模式,我们就以Spring的JdbcTemplate为例子介绍模板模式,在介绍JdbcTemplate之前我们可以先想一下Java对数据库的操作包括哪几部分?数据库的操作可以简单的分为以下几部分:数据源的配置,数据库连接的获取,SQL的执行,结果集的处理,这四步只有结果集会有不同的处理,前三个执行逻辑基本上都是一样的。因此我们可以将前三步封装成一个模板即JdbcTemplate,结果集的实现则交给调用者自己实现。
简单模板模式实现,可以看我另外一篇:设计模式--【模板模式】
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;
}
});
利用模板方法将相同处理逻辑的代码放到抽象父类中,以提高代码的复用性。将不同的代码使用不同的子类实现,通过对子类的扩展增加新的行为,提高代码的扩展性。很好的实现了开闭原则。但是随着不同代码的增多,需要每一个抽象类都需要一个子类来实现,这样导致类的个数增加。增加了系统的复杂性。
案例
一、使用模板方法模式统一调用外部服务接口
/**
* @Title:调用外部服务基类
*/
public abstract class AbstractInvokeOuterSystemService<T extends OuterBaseRequest,R extends OuterBaseResponse> implements InvokeOuterSystemService<T,R>{
@Override
public R request(T t) throws Exception {
//1. 创建请求报文(不用的服务接口可能要进行的入参校验等,需要子类实现)
String reqMessage = this.createMessage(t);
//2. 发送请求报文(这个步骤可以共用)
String respMessage = HttpClientUtil.doPostJson("http://url", reqMessage);
//3. 将响应报文解析为响应信息对象(不用的服务接口需要响应不同的信息,需要子类实现)
R resp = this.parseResponse(respMessage);
return resp;
}
protected abstract String createMessage(T t);
protected abstract R parseResponse(String responseMsg);
}
二、使用模板方法模式对外提供的统一接口服务
/**
* @Title:对外提供的统一接口服务基类
*/
public abstract class AbstractFacadeApiService<T extends BaseRequest,R> implements FacadeApiService<JSONObject>{
/**
* 处理业务请求
* @param requestMessage 请求报文对象(这个请求报文对象需要在Controller中对请求json报文做初步的解析,才可以得到)
* @return
*/
@Override
public String handle(RequestMessage<JSONObject> requestMessage) {
//这里可以做一些入参校验、验签等,主要针对请求头信息
//1. 解析请求体信息
T t = this.parseJSONObject(requestMessage.getData());
//2. 执行业务逻辑(不同的服务接口处理逻辑不同,所以)
ResponseMessage<R> responseMessage = this.doHandle(t);
//3.将响应信息序列化为json串(此步骤可以共用)
String responseMsg = JSONObject.toJSONString(responseMessage);
return responseMsg;
}
protected abstract ResponseMessage<R> doHandle(T req);
protected abstract T parseJSONObject(JSONObject jsonObject);
}
三、使用模板方法模式实现通知服务
/**
* @Title:通知服务基类
*/
public abstract class AbstractNoticeService implements NoticeService<NoticeResult>{
/**
* 模拟数据字典中 不同系统的通知地址
*/
private static Map<String,String> noticeUrlMap = new HashMap<>();
static {
noticeUrlMap.put("aaa","http://xxx/aaa/");
noticeUrlMap.put("bbb","http://xxx/bbb/");
}
@Override
public NoticeResult notice(NoticeMessage noticeMessage) throws Exception {
NoticeResult noticeResult = null;
//1.获取通知地址(不同系统的通知地址一般可以配置在数据字典中,可以从数据字典中获取)
String noticeUrl = noticeUrlMap.get(noticeMessage.getSystemCode());
//2.构建通知内容
String noticeContent = this.buildNoticeContent(noticeMessage.getUniqueNo());
//3.发送通知内容
String result = HttpClientUtil.doPostJson(noticeUrl, noticeContent);
if(!StringUtils.isEmpty(result)){
try{
noticeResult = JSON.parseObject(result, NoticeResult.class);
}catch (Exception e){
noticeResult = new NoticeResult("9001", "The result of notice is empty!");
}
}else{
noticeResult = new NoticeResult("9001", "The noticeUrl is empty!");
}
//4.更新通知状态
this.updateNoticeStatus(noticeResult, noticeMessage);
return noticeResult;
}
/**
* 更新通知状态
* @param noticeResult 通知结果
* @param noticeMessage 通知的消息信息
*/
protected abstract void updateNoticeStatus(NoticeResult noticeResult, NoticeMessage noticeMessage);
/**
* 根据通知消息ID构建消息通知内容
* @param uniqueNo
* @return
*/
protected abstract String buildNoticeContent(String uniqueNo);
}
四、使用模板方法模式实现数据访问接口
/**
* @Title:数据操作服务基类(继承了BaseService中的泛型类型)
* 增删改查的实现需要两步:1.获取对应的Mapper 2.调用Mapper对应的方法实现增删改查
*/
public abstract class AbstractService<K,T> implements BaseService<K,T>{
protected abstract BaseMapper<K,T> getBaseMapper();
@Override
public T selectByPrimaryKey(K k){
return (T) getBaseMapper().selectByPrimaryKey(k);
}
@Override
public List<T> selectList(T t){
return getBaseMapper().selectList(t);
}
@Override
public int deleteByPrimaryKey(K k){
return getBaseMapper().deleteByPrimaryKey(k);
}
@Override
public int insert(T t){
return getBaseMapper().insert(t);
}
@Override
public int updateByPrimaryKeySelective(T t){
return getBaseMapper().updateByPrimaryKeySelective(t);
}
}
模版类已经实现了父接口中的抽象方法,子类只需要继承模板类,专注实现子接口中定义的抽象方法即可
/**
* @Title:账户信息服务接口实现类
*/
@Service
public class AccountInfoServiceImpl extends AbstractService<Long, AccountInfo> implements AccountInfoService {
//这里需要注入AccountInfoMapper
@Override
protected BaseMapper<Long, AccountInfo> getBaseMapper() {
return null;
}
}
五、使用模板方法模式实现redis分布式锁
// 获取账户锁
long lockId = rCustomerAccountLock.nextId();
String customerIdStr = String.valueOf(myCustomerId);
if (!rCustomerAccountLock.tryLock(customerIdStr, lockId, tryTimeout, TimeUnit.MILLISECONDS)) {
throw new KnowledgeException("该账户已经被锁定,请稍后");
}
try {
// 具体业务处理
...
} finally {
rCustomerAccountLock.unlock(customerIdStr, lockId);
}
总结
(1)模板方法的优点:
1、模板方法模式通过把不变的行为搬移到超类,去除了子类中的重复代码。
2、子类实现算法的某些细节,有助于算法的扩展。
3、 通过一个父类调用子类实现的操作,通过子类扩展增加新的行为,符合“开放-封闭原则”。
(2)模板方法的不足:
1、每个不同的实现都需要定义一个子类,这会导致类的个数的增加,设计更加抽象。
(3)什么工具干什么活,所以每个设计模式或者思想都有它的用武之地,而模板方法的使用场景是:
1、 在某些类的算法中,用了相同的方法,造成代码的重复。
2、 控制子类扩展,子类必须遵守算法规则。