1.定義
允許子類對父類的一個或多個步驟進行重寫。例如聚合支付場景中有很多共同的步驟,比如驗簽、四要素驗證、風控等等,但是在支付的時候走不同的渠道可能在調用和參數上有很大的不同,比如有的是xml,有的是json,等等。 我們就可以用父類實現通用的邏輯,由子類實現不同的交互邏輯。
2.模板方法+鈎子函數示例
頂層接口 BasePay
public interface BasePay { //移動支付 void mobilePay(); }
抽象類 AbstractBasePay
public abstract class AbstractBasePay implements BasePay { @Override public final void mobilePay() { // 鈎子函數 if (isCheckAuth()) { checkAuth(); } checkParam(); chenckRisk(); channlPay(); } private void checkParam() { System.out.println("檢查參數"); } private void checkAuth() { System.out.println("支付權限校驗"); } private void chenckRisk() { System.out.println("風控校驗"); } //渠道支付 abstract void channlPay(); //鈎子函數,子類可以覆寫,來選擇手開啟支付權限校驗 默認不開啟 boolean isCheckAuth() { return true; } }
具體實現 1 中金支付
public class CPCNchannelPay extends AbstractBasePay{ @Override void channlPay() { System.out.println("中金支付"); } boolean isCheckAuth() { return false; } }
具體實現2 阿里支付
public class AliChannelPay extends AbstractBasePay{ @Override void channlPay() { System.out.println("阿里pay"); } }
運行及結果
public class TestPay { public static void main(String[] args) { System.out.println("--中金支付start"); BasePay pay1 = new CPCNchannelPay(); pay1.mobilePay(); System.out.println("--中金支付end"); System.out.println("--阿里paystart"); BasePay pay2 = new AliChannelPay(); pay2.mobilePay(); System.out.println("--阿里payend"); } }
結果:
--中金支付start 檢查參數 風控校驗 中金支付 --中金支付end --阿里paystart 支付權限校驗 檢查參數 風控校驗 阿里pay --阿里payend
我們可以看到中金支付中,我們把鈎子返回設置成了false ,就沒有進行支付權限校驗
4.模板方法在框架源碼中的應用
比如mybatis 對於入參設值,和返回參數映射成實體類時的類型轉換,就是用的模板方法。
類圖如下:
說明:
① TypeHandler接口
這個接口有三個方法,一個set,用來給PreparedStatement對象對應的列設置參數;兩個get,從ResultSet和CallableStatement獲取對應列的值,不同之處是一個是取第幾個位置的值,一個是取具體列名所對應的值。set用來將Java對象中的數據類型轉換為JDBC中對應的數據類型,get用來將JDBC中對應的數據類型轉換為Java對象中的數據類型轉換。
②BaseTypeHandler抽象類
在進行軟件設計時提倡面向接口的設計,但接口只是一個接口,並不做任何實質性的操作,還需有一系列的實現才可以真正的達到目標。BaseTypeHandler類便是對TypeHandler接口的初步實現,在實現TypeHandler接口的三個函數外,又引入了3個抽象函數用於null值的處理。
④DateTypeHandler
書接上文,BaseTypeHandler類也是一個抽象類,按照Java的規定抽象類並不能初始化,也不能直接使用,因而還需要有具體的類。在type包中有十多個具體的類來具體處理類型轉換,每一個類處理一個數據類型,像long、int、double等等,我們以一個稍微復雜些的DateTypeHandler類為例,了解下對日期是如何進行處理的。
1)setNonNullParameter
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, new java.sql.Timestamp(((Date) parameter).getTime())); }
首先將參數parameter這個Object轉換為Date類型,而后通過Date對象的getTime()將日期轉為毫秒數,而后再將毫秒數轉換為java.sql.Timestamp對象。即將java.util.Date對象轉換為java.sql.Timestamp對象。
2)getNullableResult
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { java.sql.Timestamp sqlTimestamp = rs.getTimestamp(columnName); if (sqlTimestamp != null) { return new java.util.Date(sqlTimestamp.getTime()); } return null; } public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { java.sql.Timestamp sqlTimestamp = cs.getTimestamp(columnIndex); if (sqlTimestamp != null) { return new java.util.Date(sqlTimestamp.getTime()); } return null; }
從上面的代碼可以看出這兩個函數的作用就是將 java.sql.Timestamp對象轉換為 java.util.Date對象.
1)setNonNullParameter
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setTimestamp(i, new java.sql.Timestamp(((Date) parameter).getTime())); }
首先將參數parameter這個Object轉換為Date類型,而后通過Date對象的getTime()將日期轉為毫秒數,而后再將毫秒數轉換為java.sql.Timestamp對象。即將java.util.Date對象轉換為java.sql.Timestamp對象。
2)getNullableResult
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { java.sql.Timestamp sqlTimestamp = rs.getTimestamp(columnName); if (sqlTimestamp != null) { return new java.util.Date(sqlTimestamp.getTime()); } return null; } public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { java.sql.Timestamp sqlTimestamp = cs.getTimestamp(columnIndex); if (sqlTimestamp != null) { return new java.util.Date(sqlTimestamp.getTime()); } return null; }
從上面的代碼可以看出這兩個函數的作用就是將 java.sql.Timestamp對象轉換為 java.util.Date對象.
參考博客:https://www.cnblogs.com/sunzhenchao/archive/2013/04/09/3009431.html