設(shè)計(jì)模式-模板方法

Template Pattern

認(rèn)識(shí)模板方法

在一個(gè)方法中定義一個(gè)算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。

案例

【Head First】案例UML圖

template-pattern-class-diagram.png

【Head First】案例代碼

關(guān)于代碼的注釋和說明,放在對(duì)應(yīng)的代碼里面更方便理解。

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

    /**
     * 定義制作咖啡因飲料的算法步驟:把水煮沸 -> 沖泡 -> 把飲料倒進(jìn)杯子 -> 加調(diào)料 算法的每一個(gè)步驟都被一個(gè)方法代表了。
     * brew()和addCondiments()方法需要子類來提供實(shí)現(xiàn)。
     */
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

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

    /**
     * 加調(diào)料
     */
    abstract void addCondiments();

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

    void pourInCup() {
        System.out.println("把飲料倒進(jìn)杯子");
    }

    /**
     * 鉤子方法 可以讓子類有能力對(duì)算法的 不同點(diǎn)進(jìn)行掛鉤。
     * 鉤子的目的:子類實(shí)現(xiàn)算法的可選的部分;子類能夠有機(jī)會(huì)對(duì)模板方法中的某些即將發(fā)生的步驟做出反應(yīng)。
     * 通過鉤子的控制,子類可以控制是否執(zhí)行算法里面的某個(gè)步驟, 例如飲料是否要加調(diào)料,飲料是否需要配送
     */
    boolean customerWantsCondiments() {
        return true;
    }

}
  • Tea.java
public class Tea extends CaffeineBeverage {

    /**
     * 茶在這里作為子類的角色,提供了沖泡步驟的實(shí)現(xiàn)
     */
    @Override
    void brew() {
        System.out.println("沖泡茶");

    }

    /**
     * 茶在這里作為子類的角色,提供了添加調(diào)料步驟的實(shí)現(xiàn)
     */
    @Override
    void addCondiments() {
        System.out.println("添加檸檬調(diào)料");
    }

}
  • Coffe.java
public class Coffee extends CaffeineBeverage {

    /**
     * 咖啡在這里作為子類的角色,提供了沖泡步驟的實(shí)現(xiàn)
     */
    @Override
    void brew() {
        System.out.println("沖泡咖啡");

    }

    /**
     * 咖啡在這里作為子類的角色,提供了添加調(diào)料步驟的實(shí)現(xiàn)
     */
    @Override
    void addCondiments() {
        System.out.println("添加糖和牛奶");
    }

}

JDK AbstractCollection的模板方法

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

  ...  

  // 添加單個(gè)元素的算法延遲到了子類實(shí)現(xiàn)
  public boolean add(E e) {
      throw new UnsupportedOperationException();
  }

  // 添加一個(gè)集合的算法步驟
  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驅(qū)動(dòng)來編寫數(shù)據(jù)庫訪問代碼,算法步驟如下:

1. 注冊(cè)并加載JDBC驅(qū)動(dòng)程序
2. 建立與數(shù)據(jù)庫的連接
3. 創(chuàng)建Statement對(duì)象
4. 執(zhí)行sql語句
5. 處理sql結(jié)果集
6. 異常處理
7. 釋放資源

對(duì)于數(shù)據(jù)庫的每次訪問,都要經(jīng)過上面7個(gè)步驟,寫一兩次問題不大,現(xiàn)在的程序邏輯一般都要和DB做交互,每次交互都走遍7個(gè)步驟,關(guān)鍵是大部分代碼都是相同的。
要解決這個(gè)問題,運(yùn)用面向?qū)ο蟮脑瓌t,區(qū)分不變的步驟和變化的步驟。上面7個(gè)步驟里面只有第4個(gè)和第5個(gè)步驟是會(huì)根據(jù)業(yè)務(wù)場(chǎng)景變化的。
Spring將數(shù)據(jù)訪問過程中不變的和變化的部分明確劃分為兩個(gè)不同的類:模板(template)和回調(diào)(callback)。模板類管理數(shù)據(jù)訪問過程中不變的部分,例如事務(wù)控制,資源管理和異常處理?;卣{(diào)類處理自定義的數(shù)據(jù)訪問代碼,例如SQL語句,綁定參數(shù)和處理結(jié)果集。

+------------------------+         +-----------------------+
|                        |         |                       |
|         模 板 類        |         |        回 調(diào) 類       |
|                        |         |                       |
|  +------------------+  |         | +------------------+  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |   1、準(zhǔn) 備 資 源  |  |         | |                  |  |
|  |                  +-------------->   3、執(zhí) 行 事 務(wù)  |  |
|  |   2、開 始 事 務(wù)  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  +------------------+  |         | +------------------+  |
|                        |         |                       |
|  +------------------+  |         | +------------------+  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |  5、提 交 / 回 滾 |  |         | |                  |  |
|  |                  <--------------+  4、返 回 數(shù) 據(jù)   |  |
|  |  6、關(guān) 閉 資 源   |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  |                  |  |         | |                  |  |
|  +------------------+  |         | +------------------+  |
|                        |         |                       |
+------------------------+         +-----------------------+

  • JdbcTemplate.java
/**
 * 模板方法的設(shè)計(jì)模式一般是通過繼承來實(shí)現(xiàn),但是這里并沒有采用繼承讓子類承擔(dān)某個(gè)算法的步驟,而是采用了組合委托給另外一個(gè)類來負(fù)責(zé)某個(gè)算法的步驟。
 * 而組合的方式也不是在JdbcTemplate類中聲明固定的變量名,而是通過傳參的方式來實(shí)現(xiàn)。
 *
 * queryXXX()和updateXXX()方法最終都轉(zhuǎn)到executeXXX()方法。
 * execute()方法就是Template Pattern里面的Template Method,定義了訪問數(shù)據(jù)庫的算法骨架:獲取數(shù)據(jù)庫連接 -> 創(chuàng)建Statement對(duì)象 -> 執(zhí)行SQL -> 捕獲異常 -> 釋放資源
 * 對(duì)于【執(zhí)行SQL】這個(gè)算法步驟,JdbcTemplate委托給StatementCallback對(duì)象action來處理。
 * 程序員就可以只關(guān)注自己的代碼邏輯,而不必關(guān)注管理資源和異常處理等問題。
 *
 */
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()方法里面的內(nèi)部類,共有4個(gè):QueryStatementCallback,UpdateStatementCallback,ExecuteStatementCallback,BatchUpdateStatementCallback。
下來列舉其中兩個(gè)類的定義:

class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
    public T doInStatement(Statement stmt) throws SQLException {
        ResultSet rs = null;
        try {
      // 查詢類的執(zhí)行需要處理結(jié)果集
            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 {
    // 更新類的可以獲取到更新的行數(shù)
        int rows = stmt.executeUpdate(sql);
        if (logger.isDebugEnabled()) {
            logger.debug("SQL update affected " + rows + " rows");
        }
        return rows;
    }
    public String getSql() {
        return sql;
    }
}

總結(jié)

適用范圍

  • 「模板方法」定義了算法的步驟,把某些步驟的實(shí)現(xiàn)延遲到子類
  • 模板方法模式為我們提供了一種代碼復(fù)用的重要技巧
  • 模板方法的抽象類可以定義具體方法、抽象方法和鉤子
  • 抽象方法由子類實(shí)現(xiàn)
  • 鉤子是一個(gè)方法,它在抽象類中不做事,或者只做默認(rèn)的事情,子類可以選擇要不要覆蓋它
  • 為了防止子類改變模板方法中的算法,可以將模板方法聲明為final
  • 模板方法的實(shí)踐應(yīng)用中有可能會(huì)有很多變化
  • 工廠方法是模板方法的一種特殊版本

與策略模式的比較

  • 兩個(gè)模式都是封裝算法,一個(gè)用組合,一個(gè)用繼承。

體現(xiàn)的OO原則

Don't call me, I will call you.

好萊塢原則:將決策權(quán)放在高層模塊中,以便決定如何以及何時(shí)調(diào)用底層模塊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容