模板方法模式

321 阅读3分钟

定义

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses define certain steps of an algorithm without changing the algorithm's structure.

这里的意思是定义一个操作中的算法框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可冲定义该算法的某些特定步骤。

目的

  1. 使用模板方法模式可以避免编写重复代码,以便开发人员可以专注于核心业务逻辑的实现
  2. 解决接口与接口实现类之间继承矛盾的问题

代码举例

比如现在在业务逻辑中有查询的场景,通常定义一个CommonQueryService接口之后直接实现就好了,如:

public class CommonQueryServiceSimpleImpl implements CommonQueryService {
    @Override
    public CommonResult<CommonInfo> queryByUser(CommonRequest request) {

        /**
         * 业务逻辑
         */
        CommonResult<CommonInfo> result = new CommonResult<>();

        return result;
    }
}

如果只有一个查询场景还好,但是如果有多个查询场景,每次查询的维度不相同,那应该怎么做到避免写一些重复的代码呢?这里的话就可以用到模板方法模式,把查询的场景抽象出一个模板,分成参数校验->查询预处理->执行业务逻辑->查询后置处理,这样做的好处是,以后的查询场景只需要重写这几个逻辑就行了,关于异常的捕获、失败日志的打印这些通用逻辑全都在模板类里就好。

模板类

public class CommonQueryTemplate {

    private final static String BIZ_DESC = "查询业务";

    public static <R, M> CommonResult<M> query(R request,
                                               AbstractInvokeExecutor<R, M> invoke) {

        CommonResult<M> result = new CommonResult<M>();

        /**
         * 打一些日志
         */
        
        try {
            
            // 业务校验方法
            invoke.checkParam();

            CommonQueryContext context = new CommonQueryContext();

            // 预处理
            invoke.preProcess(request, context);

            // 执行业务逻辑
            M model = invoke.query(context);

            // 将业务处理完成的结果进行封装处理
            result.setResultObject(model);

            //后置处理
            invoke.postProcess(result, context);
        } catch (Exception e){
            
        } catch (Throwable t){
            
        } finally {
            // 可以打一些日志,比如运行时间长度
        }
        return result;
    }
}

这里的实现有些不同,模板类并不是一个抽象类。这里的做法是额外定义了一个AbstractInvokeExecutor抽象类,把需要实现的方法包在InvokeExecutor里。

AbstractInvokeExecutor

通用的AbstractInvokeExecutor抽象类

public abstract class AbstractInvokeExecutor<R, M> {
    /**
     * 校验方法
     */
    protected abstract void checkParam();

    /**
     * 请求预处理
     */
    protected abstract void preProcess(R request, CommonQueryContext context);

    /**
     * 业务逻辑执行方法
     */
    protected abstract M query(CommonQueryContext context);

    /**
     * 后置处理
     */
    protected abstract void postProcess(CommonResult<M> result, CommonQueryContext context);
}

CommonQueryServiceImpl

接口的真正实现,实例化一个AbstractInvokeExecutor实现抽象方法就好

public class CommonQueryServiceImpl implements CommonQueryService {
    @Override
    public CommonResult<CommonInfo> queryByUser(CommonRequest request) {

        CommonResult<CommonInfo> result = CommonQueryTemplate.query(request,
                new AbstractInvokeExecutor<CommonRequest, CommonInfo>() {
                    @Override
                    protected void checkParam() {

                    }

                    @Override
                    protected void preProcess(CommonRequest request, CommonQueryContext context) {

                    }

                    @Override
                    protected CommonInfo query(CommonQueryContext context) {
                        return null;
                    }

                    @Override
                    protected void postProcess(CommonResult<CommonInfo> result, CommonQueryContext context) {

                    }
                });
        return null;
    }
}

源码举例

JDBCTemplate

query

    public <T> T query(
            PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
            throws DataAccessException {

        Assert.notNull(rse, "ResultSetExtractor must not be null");
        logger.debug("Executing prepared SQL query");

        return execute(psc, new PreparedStatementCallback<T>() {
            @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();
                    }
                }
            }
        });
    }

extractData

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()) {
    results.add(this.rowMapper.mapRow(rs, rowNum++));
  }
  return results;
}

可以看到固定的代码都已经帮我们写好了,帮我们做了获取连接,获取驱动,获取statement,关闭连接等。

我们只需要自己来实现对应的RowMapper做结果集映射就行了。

总结

优点

  • 新增功能不需要重写所有逻辑代码,只要实现抽象类的方法即可
  • 行为是由父类控制的,子类通过扩展的方式增加相应功能,符合开闭原则

缺点

  • 继承关系自身的缺点,如果父类添加新的抽象方法,所有子类都要修改一遍