设计模式-模板方法模式学习之旅

“这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

一、什么是模板方法模式?

模板方法模式(Template Method Pattern),是指定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤,属于行为型设计模式。

模板方法模式实际上是封装了一个固定流程,该流程由几个步骤组成,具体步骤可以由子类进行不同实现,从而让固定的流程产生不同的结果。它非常简单,其实就是类的继承机制,但它却是一个应用非常广泛的模式。模板方法模式的本质是抽象封装流程,具体进行实现

二、模板方法模式的应用场景

当完成一个操作具体固定的流程时,由抽象固定流程步骤,具体步骤交给子类进行具体实现(固定流程,不同的实现)。在我们的日常生活中模板方法模式也很常见。

比如我们平时办理入职流程填写入职登记表 -> 打印简历 -> 复印学历 -> 复印身份证 -> 签订劳动合同 -> 建立花名册 -> 办理工牌 -> 安排工位等;

再比如我们平时在家里炒菜:洗锅 -> 点火 -> 热锅 -> 上油 -> 下原料 -> 翻炒 -> 放调料 -> 出锅;

再比如有个小品,赵本山问宋丹丹:“如何把大象放进冰箱?” 宋丹丹回答:“第一步:打开冰箱,第二步:把大象塞进冰箱,第三步:关闭冰箱门”。赵本山再问:“怎么把长颈鹿放进冰箱?” 宋丹丹回答:“第一步:打开冰箱门,第二步:把大象拿出来,第三步:把长颈鹿塞进去,第四步:关闭冰箱门”

image.png

模板方法模式使用于以下应用场景:

  1. 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  2. 各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复。

三、模板方法模式涉及的角色

首先来看下模板方法模式的通用UML类图:

image.png

从UML类图中,我们可以看到,模板方法模式主要包含两种角色:

  1. 抽象模板(AbstractClass):抽象模板类,定义了一套算法框架/流程。
  2. 具体实现(ConcreteClass):具体实现类,对算法框架/流程的某些步骤进行了实现。

四、模板方法模式业务案例

1. 模板方法模式中的钩子方法

我们还是以课程创建流程为例:发布预习资料 -> 制作课件PPT -> 在线直播 -> 提交课堂笔记 -> 提交源码 -> 布置作业 -> 检查作业。首先我们来创建AbstractCourse抽象类:

public abstract class AbstractCourse {

    public final void createCourse() {
        //1.发布预习资料
        postPreResource();

        //2.制作课件
        createPPT();

        //3.直播授课
        liveVideo();

        //4.上传课后资料
        postResource();

        //5.布置作业
        postHomework();

        if (needCheckHomework()) {
            checkHomework();
        }
    }

    protected abstract void checkHomework();

    //钩子方法
    protected boolean needCheckHomework() {
        return false;
    }

    protected void postHomework() {
        System.out.println("布置作业");
    }

    protected void postResource() {
        System.out.println("上传课后资料");
    }

    protected void liveVideo() {
        System.out.println("直播讲课");
    }

    protected void createPPT() {
        System.out.println("制作课件");
    }

    protected void postPreResource() {
        System.out.println("发布预习资料");
    }
}
复制代码

上面的代码中有个钩子方法可能有些小伙伴还不是很理解,在此我稍作解释。设计钩子方法的主要目的是用来干预执行流程,使得我们控制行为流程更加灵活,更符合实际业务的需求。钩子方法的返回值一般为适合条件分支语句的返回值(如boolean、int等)。小伙伴们可以根据自己的业务场景来决定是否需要使用钩子方法。

接下来创建JavaCourse类:

public class JavaCourse extends AbstractCourse {

    private boolean needCheckHomework = false;

    public void setNeedCheckHomework(boolean needCheckHomework) {
        this.needCheckHomework = needCheckHomework;
    }

    @Override
    protected boolean needCheckHomework() {
        return this.needCheckHomework;
    }

    @Override
    protected void checkHomework() {
        System.out.println("检查Java作业");
    }
}
复制代码

创建PythonCourse类:

public class PythonCourse extends AbstractCourse {
    @Override
    protected void checkHomework() {
        System.out.println("检查Python作业");
    }
}
复制代码

客户端测试代码:

public class Test {

    public static void main(String[] args) {
        System.out.println("===========架构师课程==========");
        JavaCourse javaCourse = new JavaCourse();
        javaCourse.setNeedCheckHomework(true);
        javaCourse.createCourse();

        System.out.println("===========Python课程==========");
        PythonCourse pythonCourse = new PythonCourse();
        pythonCourse.createCourse();
    }
}
复制代码

运行结果如下:

image.png

通过这样一个案例,相信小伙伴们对模板方法模式有了一个基本的印象。

2. 利用模板方法模式重构JDBC操作业务场景

创建一个模板类JdbcTemplate,封装所有的JDBC操作。以查询为例,每次查询的表不同,返回的数据结构也就不一样。我们针对不同的数据,都要封装成不同的实体对象。而每个实体封装的逻辑都是不一样的,但封装前后的处理流程是不变的,因此我们可以使用模板方法模式来设计这样的业务场景。

先创建约束ORM逻辑的接口RowMapper:

public interface RowMapper<T> {

    T mapRow(ResultSet rs, int rowNum) throws Exception;
}
复制代码

在创建封装了所有处理流程的抽象类JdbcTemplate:

public abstract class JdbcTemplate<T> {

    private DataSource dataSource;

    public JdbcTemplate(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public final List<T> executeQuery(String sql, RowMapper<T> rowMapper, Object[] values) {
        try {
            //1.获取连接
            Connection conn = this.getConnection();
            //2.创建语句集
            PreparedStatement ps = this.createPrepareStatement(conn, sql);
            //3.执行语句集
            ResultSet rs = this.executeQuery(ps, values);
            //4.处理结果集
            List<T> result = this.parseResultSet(rs, rowMapper);
            //5.关闭结果集
            rs.close();
            //6.关闭语句集
            ps.close();
            //7.关闭连接
            conn.close();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private List<T> parseResultSet(ResultSet rs, RowMapper<T> rowMapper) throws Exception {
        List<T> result = new ArrayList<>();
        int rowNum = 0;
        while (rs.next()) {
            result.add(rowMapper.mapRow(rs, rowNum++));
        }
        return result;
    }

    private ResultSet executeQuery(PreparedStatement ps, Object[] values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            ps.setObject(i, values[i]);
        }
        return ps.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<Member> {

    public MemberDao(DataSource dataSource) {
        super(dataSource);
    }

    public List<Member> selectAll() {
        String sql = "select * from t_member";
        return super.executeQuery(sql, new RowMapper<Member>() {
            @Override
            public Member mapRow(ResultSet rs, int rowNum) throws Exception {
                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 class Test {

    public static void main(String[] args) {
        MemberDao memberDao = new MemberDao(null);
        List<Member> result = memberDao.selectAll();
        System.out.println(result);
    }
}
复制代码

希望通过这两个案例的业务场景分析,能够帮助小伙伴对模板方法模式有更深的理解。

五、模板方法模式在源码中的体现

在Mybatis框架也有一些经典的应用,我们来看一下BaseExecutor类,它是一个基础的SQL执行类,实现了大部分的SQL执行逻辑,然后把几个方法交给了子类定制化完成,源码如下:

public abstract class BaseExecutor implements Executor {

......

  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

......

}
复制代码

如doUpdate、doFlushStatements、doQuery、doQueryCursor这几个方法就是交由子类来实现,那么BaseExecutor有哪些子类呢?我们来看一下它的类图:

image.png

我们一起来看一下SimpleExecutor的doUpdate实现:

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
复制代码

再来对比一下BatchExecutor的doUpdate实现:

public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
        handler.parameterize(stmt);//fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt); //fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
  // handler.parameterize(stmt);
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
复制代码

六、模板方法模式的优缺点

优点:

  1. 利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。
  2. 将不同的代码放到子类中,通过对子类的扩展增加新的行为,提高代码的扩展性。
  3. 把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。

缺点:

  1. 类数目的增加,每一个抽象类都需要一个子类来实现,这样导致类的个数增加。
  2. 类数量的增加,间接地增加了系统实现的复杂度。
  3. 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍。

模板方法模式比较简单,相信小伙伴们肯定能学会,也肯定能够理解好,只要勤加练习,多结合业务场景思考问题,就能够把模板方法模式运用好。

七、友情链接

设计模式-工厂模式学习之旅

设计模式-单例模式学习之旅

设计模式-原型模式学习之旅

设计模式-建造者模式学习之旅

设计模式-代理模式学习之旅

设计模式-门面模式学习之旅

设计模式-装饰器模式学习之旅

设计模式-享元模式学习之旅

设计模式-组合模式学习之旅

设计模式-适配器模式学习之旅

设计模式-桥接模式学习之旅

设计模式-委派模式学习之旅

欢迎大家关注微信公众号(MarkZoe)互相学习、互相交流。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享