- 定义:定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现
- 补充:模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤
- 类型:行为型
- 适用场景:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
- 各子类中公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复
- 优点:
- 提高复用性
- 提高扩展性
- 符合开闭原则
- 缺点:
- 类数目的增加
- 增加了系统实现的复杂度
- 继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍
- 扩展:钩子方法,提供了缺省的行为,子类在必要时进行扩展
- 相关设计模式:
- 模板方法和工厂方法模式:工厂方法是模板方法的一种特殊实现
- 模板方法和策略模式:策略模式目的是使不同的算法可以相互替换,并且不影响应用层客户端的使用,而模板方法模式是针对定义一个算法的流程,而将一些不太一样的实现步骤交给子类实现,模板方法不改变算法流程,策略模式可以改变算法流程
父类固定算法步骤
/** * <p>定义抽象算法类</p> * */ public abstract class AbstractCalculator { /**方法加final修饰下,防止子类恶意修改算法结构*/ public final double calculate(String formula, String splitChar) { // 1、第一步,先根据计算公式和分隔符,拿到要计算的数值(假设这个是核心,不对外公布) double[] splits = split(formula, splitChar); // 2、第二步,各个算法类代表发个言吧(默认不发言的,一律按父类说的来) hookSay(); // 3、第三步,话说完了,各个算法(子)类开始干活吧(这时候别再指望父类帮你默认实现了,休想!) return calculate(splits[0], splits[1]); } /** * <p>根据字符分割公式,提取double数组</p> * * @param formula 计算公式 * @param splitChar 分隔字符 * @return double数组 */ private double[] split(String formula, String splitChar) { String array[] = formula.trim().split(splitChar); double arrayDouble[] = new double[2]; arrayDouble[0] = Double.parseDouble(array[0]); arrayDouble[1] = Double.parseDouble(array[1]); return arrayDouble; } /** * <p>公开(钩子)方法,子类和父类进行信息交互,子类可以使用默认的父类方法,也可以重写改方法</p> */ public void hookSay() { System.out.println("父类说:算法步骤的顺序我已经定好了,尔等子类照着做就行!"); } /** * <p>根据a和b的值计算结果,这个必须由子类来实现,父类帮不了了</p> * * @param numA 第一个数字 * @param numB 第二个数字 * @return 两个数字计算的结果 */ protected abstract double calculate(double numA, double numB); } /** * <p>加法运算</p> * */ public class Plus extends AbstractCalculator { @Override protected double calculate(double numA, double numB) { return numA + numB; } /** * 重写父类钩子方法 */ @Override public void hookSay() { System.out.println("子类Plus说:接下来,由我来计算结果!"); } } /** * <p>减法运算</p> * */ public class Sub extends AbstractCalculator { @Override protected double calculate(double numA, double numB) { return numA - numB; } }
UML
说明:类图关系很简单,就是一个抽象模板类,然后有两个实现类
JDBC模板方法实现
/** * <p>模拟JDB模板类(可以理解为伪代码说明)</p> * */ public abstract class AbstractJdbcTemplate { public final Object execute(String sql) throws SQLException { // 1、首先获取数据库连接对象 Connection connection = getConnection(); // 2、根据连接获取statement对象(数据库操作对象) getStament(connection); // 3、使用数据库操作对象来执行对应的SQL语句 ResultSet rs = executeQuery(sql); // 4、最后基于查询的数据集,处理并返回最终结果 return doInStatement(rs); // 资源的释放这里我就省略了 } private Connection getConnection() { System.out.println("获取数据库连接"); return null; } private Statement getStament(Connection connection) { System.out.println("获取数据库操作对象"); return null; } private ResultSet executeQuery(String sql) { System.out.println("执行SQL语句:" + sql); return null; } /** * <p>基于结果集进行定制,由具体的子类实现</p> * * @param rs 结果集 * @return 数据模型对象 */ protected abstract Object doInStatement(ResultSet rs) throws SQLException; } /** * <p>商品数据库操作(模板)实现类</p> * */ public class GoodsTemplateImpl extends AbstractJdbcTemplate { @Override protected Object doInStatement(ResultSet rs) throws SQLException { // 遍历数据集 // while (rs.next()){ // // dosomething // } System.out.println("数据集处理成功,返回Goods数据实体对象"); return null; } } /** * <p>用户数据库操作(模板)实现类</p> * */ public class UserTemplateImpl extends AbstractJdbcTemplate { @Override protected Object doInStatement(ResultSet rs) throws SQLException { // 遍历数据集 // while (rs.next()){ // // dosomething // } System.out.println("数据集处理成功,返回User数据实体对象"); return null; } }
UML
JDBC模板和回调实现
/** * <p>数据库操作回调接口</p> * */ public interface IStatementCallback { Object doInStatement(Statement stmt) throws SQLException; } /** * <p>商品查询业务类</p> * */ public class GoodsQueryServiceImpl { public Object query(String sql) throws SQLException { return new JdbcTemplate().executeQuery(new GoodsQueryStatementCallBack(sql)); } } /** * <p>内部类 == 用户查询数据库操作对象回调类</p> */ class GoodsQueryStatementCallBack implements IStatementCallback { private String sql; public GoodsQueryStatementCallBack(String sql) { this.sql = sql; } @Override public Object doInStatement(Statement stmt) throws SQLException { System.out.println("执行SQL语句:" + sql); System.out.println("数据集处理成功,返回Goods数据实体对象"); return null; } } /** * <p>用户查询业务类</p> * */ public class UserQueryServiceImpl { public Object query(String sql) throws SQLException { return new JdbcTemplate().executeQuery(new UserQueryStatementCallBack(sql)); } } /** * <p>内部类 == 用户查询数据库操作对象回调类</p> */ class UserQueryStatementCallBack implements IStatementCallback { private String sql; public UserQueryStatementCallBack(String sql) { this.sql = sql; } @Override public Object doInStatement(Statement stmt) throws SQLException { System.out.println("执行SQL语句:" + sql); System.out.println("数据集处理成功,返回User数据实体对象"); return null; } } /** * <p>数据库操作模板类</p> * */ public class JdbcTemplate { public final Object execute(IStatementCallback callback) throws SQLException { // 1、首先获取数据库连接对象 Connection connection = getConnection(); // 2、根据连接获取statement对象(数据库操作对象) Statement stmt = getStament(connection); // 3、根据stmt回调子类方法 --> 执行sql语句获取最终结果 return callback.doInStatement(stmt); // 资源的释放这里我就省略了 } private Connection getConnection() { System.out.println("获取数据库连接"); return null; } private Statement getStament(Connection connection) { System.out.println("获取数据库操作对象"); return null; } public Object executeQuery(IStatementCallback callback) throws SQLException { return execute(callback); } /** * 如果该类是抽象类,则定义以下四个抽象方法,则其子类必须全部实现 * 回调的好处,即子类只需要实现父类中的某一个方法即可,该方法可以抽离成一个接口的方法 */ // protected abstract void A(); // protected abstract void B(); // protected abstract void C(); // protected abstract void D(); }
UML
测试
/** * <p>模板方法测试</p> * */ public class TemplateTest { public static void main(String[] args) throws SQLException { calculate(); jdbc(); jdbcCallBack(); } private static void calculate() { System.out.println("====================== 11 + 2 加法公式计算 "); AbstractCalculator plus = new Plus(); System.out.println(plus.calculate("11 + 2", "\\+")); System.out.println("====================== 11 - 2 减法公式计算"); AbstractCalculator sub = new Sub(); System.out.println(sub.calculate("11 - 2", "\\-")); } private static void jdbc() throws SQLException { System.out.println("====================== 用户数据层操作实现 "); AbstractJdbcTemplate userTemplate = new UserTemplateImpl(); userTemplate.execute("select * from user "); System.out.println("====================== 商品数据层操作实现 "); AbstractJdbcTemplate goodsTemplate = new GoodsTemplateImpl(); goodsTemplate.execute("select * from goods "); } private static void jdbcCallBack() throws SQLException { System.out.println("====================== 用户数据层操作实现(模板+回调) "); UserQueryServiceImpl queryService = new UserQueryServiceImpl(); queryService.query("select * from user "); System.out.println("====================== 商品数据层操作实现(模板+回调)"); GoodsQueryServiceImpl goodsQueryService = new GoodsQueryServiceImpl(); goodsQueryService.query("select * from goods "); } /** * * 百科中如此描述模板方法: * 模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。 * 让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。 * * 模板方法和策略模式有点像,都是由子类最终实现算法,二者区别是: * (1)策略模式定义了一个统一的算法接口,由具体的算法子类实现,所有的算法方法必须暴露给子类, * 由子类来实现,这样会产生一个问题,就是在执行算法之前如果有大量的数据准备工作,则会 * 增加子算法方法块代码的臃肿度,使得大量代码重复得不到复用! * (2)模板方法定一个抽象的类,由父类统一定义实现算法的步骤,具体实现延迟到子类, * 属于算法"定制",对特定的重复的算法部分实现了封装,只对扩展的部分暴露给子类! * * 优点: * 想必不用说,我们都可以看出来,模板方法的一个最大的好处就是实现了重复代码的复用! * 另外一个就是,扩展很方便! * 缺点: * 和策略模式一样,也存在子类过多的情况出现,类一多,维护起来的复杂度就增加了! * * <p>假如,一个抽象类有4个子类,你可能会说,也就才4个啊,半个小时都能看的透透彻彻了</p> * <p>假如,一个抽象类有100个子类,你可能会皱着眉头说,卧槽,这特么还是人吗,怎么写了这么多?</p> * <p>所以,类并不是越多越能体现系统的健壮性,反而多了会适得其反噢!</p> */ } =========执行结果============ ====================== 11 + 2 加法公式计算 子类Plus说:接下来,由我来计算结果! 13.0 ====================== 11 - 2 减法公式计算 父类说:算法步骤的顺序我已经定好了,尔等子类照着做就行! 9.0 ====================== 用户数据层操作实现 获取数据库连接 获取数据库操作对象 执行SQL语句:select * from user 数据集处理成功,返回User数据实体对象 ====================== 商品数据层操作实现 获取数据库连接 获取数据库操作对象 执行SQL语句:select * from goods 数据集处理成功,返回Goods数据实体对象 ====================== 用户数据层操作实现(模板+回调) 获取数据库连接 获取数据库操作对象 执行SQL语句:select * from user 数据集处理成功,返回User数据实体对象 ====================== 商品数据层操作实现(模板+回调) 获取数据库连接 获取数据库操作对象 执行SQL语句:select * from goods 数据集处理成功,返回Goods数据实体对象
源码中的应用
- java.util.AbstractList:下面的子类实现ArrayList中有个方法,和它一样的抽象模板还有java.util.AbstractSet、java.util.AbstractMap
public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); int cSize = c.size(); if (cSize==0) return false; checkForComodification(); parent.addAll(parentOffset + index, c); this.modCount = parent.modCount; this.size += cSize; return true; }
- javax.servlet.http.HttpServlet:对这个类应该很熟悉的,这个类定义了一套模板,doGet()、doPost()、doDelete()...,这也是模板方法在Servlet方法中的应用