设计模式-模板方法

Template Pattern

认识模板方法

在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

案例

【Head First】案例UML图

template-pattern-class-diagram.png

【Head First】案例代码

关于代码的注释和说明,放在对应的代码里面更方便理解。

  • CaffeineBeverage.java
/**
 * 咖啡因饮料
 *
 */
public abstract class CaffeineBeverage {

    /**
     * 定义制作咖啡因饮料的算法步骤:把水煮沸 -> 冲泡 -> 把饮料倒进杯子 -> 加调料 算法的每一个步骤都被一个方法代表了。
     * brew()和addCondiments()方法需要子类来提供实现。
     */
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    /**
     * 冲泡
     */
    abstract void brew();

    /**
     * 加调料
     */
    abstract void addCondiments();

    void boilWater() {
        System.out.println("把水煮沸");
    }

    void pourInCup() {
        System.out.println("把饮料倒进杯子");
    }

    /**
     * 钩子方法 可以让子类有能力对算法的 不同点进行挂钩。
     * 钩子的目的:子类实现算法的可选的部分;子类能够有机会对模板方法中的某些即将发生的步骤做出反应。
     * 通过钩子的控制,子类可以控制是否执行算法里面的某个步骤, 例如饮料是否要加调料,饮料是否需要配送
     */
    boolean customerWantsCondiments() {
        return true;
    }

}
  • Tea.java
public class Tea extends CaffeineBeverage {

    /**
     * 茶在这里作为子类的角色,提供了冲泡步骤的实现
     */
    @Override
    void brew() {
        System.out.println("冲泡茶");

    }

    /**
     * 茶在这里作为子类的角色,提供了添加调料步骤的实现
     */
    @Override
    void addCondiments() {
        System.out.println("添加柠檬调料");
    }

}
  • Coffe.java
public class Coffee extends CaffeineBeverage {

    /**
     * 咖啡在这里作为子类的角色,提供了冲泡步骤的实现
     */
    @Override
    void brew() {
        System.out.println("冲泡咖啡");

    }

    /**
     * 咖啡在这里作为子类的角色,提供了添加调料步骤的实现
     */
    @Override
    void addCondiments() {
        System.out.println("添加糖和牛奶");
    }

}

JDK AbstractCollection的模板方法

  • AbstractCollection.java
public abstract class AbstractCollection<E> implements Collection<E> {

  ...  

  // 添加单个元素的算法延迟到了子类实现
  public boolean add(E e) {
      throw new UnsupportedOperationException();
  }

  // 添加一个集合的算法步骤
  public boolean addAll(Collection<? extends E> c) {
      boolean modified = false;
      for (E e : c)
          if (add(e))
              modified = true;
      return modified;
  }
  
  ...
  
}
  • ArrayList.java
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    ... 
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    ...
    
}
  • HashSet.java
public class HashSet<E> extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable

    ... 
    
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    
    ...
    
}

【Spring】JdbcTemplate的UML图

template-pattern-jdbctemplate.png

【Spring】JdbcTemplate主要代码

直接使用JDBC驱动来编写数据库访问代码,算法步骤如下:

1. 注册并加载JDBC驱动程序
2. 建立与数据库的连接
3. 创建Statement对象
4. 执行sql语句
5. 处理sql结果集
6. 异常处理
7. 释放资源

对于数据库的每次访问,都要经过上面7个步骤,写一两次问题不大,现在的程序逻辑一般都要和DB做交互,每次交互都走遍7个步骤,关键是大部分代码都是相同的。
要解决这个问题,运用面向对象的原则,区分不变的步骤和变化的步骤。上面7个步骤里面只有第4个和第5个步骤是会根据业务场景变化的。
Spring将数据访问过程中不变的和变化的部分明确划分为两个不同的类:模板(template)和回调(callback)。模板类管理数据访问过程中不变的部分,例如事务控制,资源管理和异常处理。回调类处理自定义的数据访问代码,例如SQL语句,绑定参数和处理结果集。

+------------------------+         +-----------------------+
|                        |         |                       |
|         模 板 类        |         |        回 调 类       |
|                        |         |                       |
|  +------------------+  |         | +------------------+  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |   1、准 备 资 源  |  |         | |                  |  |
|  |                  +-------------->   3、执 行 事 务  |  |
|  |   2、开 始 事 务  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  +------------------+  |         | +------------------+  |
|                        |         |                       |
|  +------------------+  |         | +------------------+  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |  5、提 交 / 回 滚 |  |         | |                  |  |
|  |                  <--------------+  4、返 回 数 据   |  |
|  |  6、关 闭 资 源   |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  +------------------+  |         | +------------------+  |
|                        |         |                       |
+------------------------+         +-----------------------+

  • JdbcTemplate.java
/**
 * 模板方法的设计模式一般是通过继承来实现,但是这里并没有采用继承让子类承担某个算法的步骤,而是采用了组合委托给另外一个类来负责某个算法的步骤。
 * 而组合的方式也不是在JdbcTemplate类中声明固定的变量名,而是通过传参的方式来实现。
 *
 * queryXXX()和updateXXX()方法最终都转到executeXXX()方法。
 * execute()方法就是Template Pattern里面的Template Method,定义了访问数据库的算法骨架:获取数据库连接 -> 创建Statement对象 -> 执行SQL -> 捕获异常 -> 释放资源
 * 对于【执行SQL】这个算法步骤,JdbcTemplate委托给StatementCallback对象action来处理。
 * 程序员就可以只关注自己的代码逻辑,而不必关注管理资源和异常处理等问题。
 *
 */
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);
    }
    stmt = conToUse.createStatement();
    applyStatementSettings(stmt);
    Statement stmtToUse = stmt;
    if (this.nativeJdbcExtractor != null) {
      stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
    }
    T result = action.doInStatement(stmtToUse);
    handleWarnings(stmt);
    return result;
  }
  catch (SQLException ex) {
    // Release Connection early, to avoid potential connection pool deadlock
    // in the case when the exception translator hasn't been initialized yet.
    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());
  }
}
  • StatementCallback子类

StatementCallback子类都是定义在JdbcTemplage.execute()方法里面的内部类,共有4个:QueryStatementCallback,UpdateStatementCallback,ExecuteStatementCallback,BatchUpdateStatementCallback。
下来列举其中两个类的定义:

class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
    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);
            }
            return rse.extractData(rsToUse);
        }
        finally {
            JdbcUtils.closeResultSet(rs);
        }
    }
    public String getSql() {
        return sql;
    }
}

class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {
    public Integer doInStatement(Statement stmt) throws SQLException {
    // 更新类的可以获取到更新的行数
        int rows = stmt.executeUpdate(sql);
        if (logger.isDebugEnabled()) {
            logger.debug("SQL update affected " + rows + " rows");
        }
        return rows;
    }
    public String getSql() {
        return sql;
    }
}

总结

适用范围

  • 「模板方法」定义了算法的步骤,把某些步骤的实现延迟到子类
  • 模板方法模式为我们提供了一种代码复用的重要技巧
  • 模板方法的抽象类可以定义具体方法、抽象方法和钩子
  • 抽象方法由子类实现
  • 钩子是一个方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要覆盖它
  • 为了防止子类改变模板方法中的算法,可以将模板方法声明为final
  • 模板方法的实践应用中有可能会有很多变化
  • 工厂方法是模板方法的一种特殊版本

与策略模式的比较

  • 两个模式都是封装算法,一个用组合,一个用继承。

体现的OO原则

Don't call me, I will call you.

好莱坞原则:将决策权放在高层模块中,以便决定如何以及何时调用底层模块。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容