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