将大象装进冰箱需要三步,那么老虎了?如何优雅的将大象装进冰箱?

把大象装进冰箱

Step 大象 老虎 ...
First 打开冰箱门 打开冰箱门 打开冰箱门
Second 把大象放进去 把老虎放进去 ...
Third 关闭冰箱门 关闭冰箱门 关闭冰箱门

大象类

    public class Elephant {public void putRefrigerator() {openDoor();putElephant();closeDoor();}public void openDoor() {System.out.println("open the door");}public void putElephant() {System.out.println("put in the Elephant");}public void closeDoor() {System.out.println("close the door");}}
复制代码

老虎类

    public class Tiger {public void putRefrigerator() {openDoor();putTiger();closeDoor();}public void openDoor() {System.out.println("open the door");}public void putTiger() {System.out.println("put in the Tiger");}public void closeDoor() {System.out.println("close the door");}}
复制代码

可以看出我们将大象和老虎放进冰箱的过程中出现了大量的重复代码,这显然不是一个好的设计,如果我们在以后的系统升级过程中需要再放入长颈鹿怎么办,我们应该如何从我们的设计中删除这些重复代码?通过观察我们发现放大象和放老虎之间有很多共同点,都需要进行开关门的操作,只是放的过程不尽相同,我们是否可以将共同点抽离?我们一起试试看

抽象超类

    public abstract class AbstractPutAnyAnimal {//这是一个模板方法,它是一个算法的模板,描述我们将动物放进冰箱的步骤,每一个方法代表了一个步骤public void putRefrigerator() {openDoor();putAnyAnimal();closeDoor();}//在超类中实现共同的方法,由超类来处理public void openDoor() {System.out.println("open the door");}public void closeDoor() {System.out.println("close the door");}//每个子类可能有不同的方法,我们定义成抽象方法让子类去实现abstract void putAnyAnimal();}
复制代码

大象类

    public class Elephant extends AbstractPutAnyAnimal {//子类实现自己的业务逻辑@Overridevoid putAnyAnimal() {System.out.println("put in the Elephant");}}
复制代码

老虎类

    public class Tiger extends AbstractPutAnyAnimal {//子类实现自己的业务逻辑@Overridevoid putAnyAnimal() {System.out.println("put in the Tiger");}}
复制代码

通过将相同的方法抽离到超类中,并定义一个抽象方法供子类提供不同的实现,事实上我们刚刚实现了一个模板方法模式。

模板方法模式定义?

模板方法模式定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现,putRefrigerator 方法定义了我们将大象装进冰箱的步骤它就是一个模板方法。模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不在改变算法结构的情况下,重新定义算法的某些步骤(子类提供自己的实现)

模板方法模式中的钩子

我们可以在超类中定义一个空方法,我们称这种方法为钩子(hook)。子类可以依据情况选择覆盖,钩子的存在可以让子类有能力对算法的不同点进行挂载;钩子可以让子类实现算法中的可选部分,钩子也可以让子类为抽象类做一些决定我们将大象装进冰箱后可能会想调整冰箱温度,也可能什么都不做使用默认温度,我们可以通过定义一个钩子,让子类来选择是否调整温度,如下:

抽象父类

    public abstract class AbstractPutAnyAnimal {public void putRefrigerator() {openDoor();putAnyAnimal();closeDoor();//默认为false,重新这个方法决定是否执行addTemperature();方法if (isAdd()) {addTemperature();}}public void openDoor() {System.out.println("open the door");}public void closeDoor() {System.out.println("close the door");}abstract void putAnyAnimal();void addTemperature(){System.out.println("plus one");};//定义一个空实现,由子类决定是否对其进行实现boolean isAdd(){return false;}}
复制代码

大象类

    public class Elephant extends AbstractPutAnyAnimal {@Overridevoid putAnyAnimal() {System.out.println("put in the Elephant");}//子类实现钩子方法@Overrideboolean isAdd() {return true;}}
复制代码

我们通过定义一个钩子方法,子类选择是否实现这个钩子方法,来决定是否调整温度;当然钩子方法的用途不止如此,它还能让子类有机会对模板中即将发生或刚刚发生的步骤做出反应,这在JDK中有很多的例子,甚至在前端开发领域也有很多例子,我就不具体展开代码演示了,后面在模板方法模式的更多应用中展开。

JDK以及Spring中使用了很多的设计模式,下面我们通过比较传统JDBC编程和JDBCTemplate来看看模板方法模式是如何帮我们消除样板代码的

传统JDBC编程

JDBC编程之新增

        String driver = "com.mysql.jdbc.Driver";String url = "jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useSSL=true";String username = "root";String password = "1234";Connection connection = null;Statement statement = null;try {Class.forName(driver);connection = DriverManager.getConnection(url, username, password);String sql = "insert into users(nickname,comment,age) values('小小谭','I love three thousand times', '21')";statement = connection.createStatement();int i = statement.executeUpdate(sql);return i;} catch (SQLException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} finally {try {if (null != statement) {statement.close();}if (null != connection) {connection.close();}} catch (SQLException e) {throw new RuntimeException(e);}}return 0;
复制代码

JDBC编程之查询

        String driver = "com.mysql.jdbc.Driver";String url = "jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useSSL=true";String username = "root";String password = "1234";Connection connection = null;Statement statement = null;try{Class.forName(driver);connection = DriverManager.getConnection(url, username, password);String sql = "select nickname,comment,age from users";statement = connection.createStatement();ResultSet resultSet = statement.executeQuery(sql);List<Users> usersList = new ArrayList<>();while (resultSet.next()) {Users users = new Users();users.setNickname(resultSet.getString(1));users.setComment(resultSet.getString(2));users.setAge(resultSet.getInt(3));usersList.add(users);}return usersList;} catch (SQLException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} finally {try {if (null != statement) {statement.close();}if (null != connection) {connection.close();}} catch (SQLException e) {throw new RuntimeException(e);}}return null;
复制代码

上面给出了我们在传统JDBC编程中的两个案例,可以看到传统JDBC的很多缺点,当然在实际项目中我们可能不会这么原始的进行数据库开发,可能会对JDBC进行一定的封装,方便我们的使用。Spring 官方为了简化JDBC的开发也发布了JDBCTemplate,下面我们就看一下它是如何简化开发的,以及模板方法模式在其中的应用

JDBCTemplate是个啥,它到底简化了什么?

从JDBCTemplate的名字我们就不难看出,它简化了我们JDBC的开发,而且很可能大量应用了模板方法模式,它到底为我们提供了什么?它提供了与平台无光的异常处理机制。使用过原生JDBC开发的同学可能有经历,几乎所有的操作代码都需要我们强制捕获异常,但是在出现异常时我们往往无法通过异常读懂错误。Spring解决了我们的问题它提供了多个数据访问异常,并且分别描述了他们抛出时对应的问题,同时对异常进行了包装不强制要求我们进行捕获,同时它为我们提供了数据访问的模板化,从上面的传统JDBC编程我们可以发现,很多操作其实是重复的不变得比如事务控制、资源的获取关闭以及异常处理等,同时结果集的处理实体的绑定,参数的绑定这些东西都是特有的。因此Spring将数据访问过程中固定部分和可变部分划分为了两个不同的类(Template)和回调(Callback),模板处理过程中不变得部分,回调处理自定义的访问代码;下面我们具体通过源码来学学习一下

模板方法模式在JDBCTemplate中的应用

我所使用的版本是5.1.5.RELEASE

打开JdbcTemplate类(我这里就不截图了,截图可能不清晰我直接将代码copy出来):

JdbcTemplate

    public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {//查询前缀private static final String RETURN_RESULT_SET_PREFIX = "#result-set-";//计数前缀private static final String RETURN_UPDATE_COUNT_PREFIX = "#update-count-";//是否跳过警告private boolean ignoreWarnings = true;//查询大小private int fetchSize = -1;//最大行private int maxRows = -1;//查询超时private int queryTimeout = -1;//是否跳过结果集处理private boolean skipResultsProcessing = false;//是否跳过非公共结果集处理private boolean skipUndeclaredResults = false;//map结果集是否大小写敏感private boolean resultsMapCaseInsensitive = false;public JdbcTemplate() {}//调用父类方法设置数据源和其他参数public JdbcTemplate(DataSource dataSource) {this.setDataSource(dataSource);this.afterPropertiesSet();}//调用父类方法设置数据源,懒加载策略和其他参数public JdbcTemplate(DataSource dataSource, boolean lazyInit) {this.setDataSource(dataSource);this.setLazyInit(lazyInit);this.afterPropertiesSet();}}
复制代码

JdbcTemplate 继承了JdbcAccessor实现了JdbcOperations,JdbcAccessor主要封装了数据源的操作,JdbcOperations主要定义了一些操作接口。我们一起看一下JdbcOperations类;

    public abstract class JdbcAccessor implements InitializingBean {protected final Log logger = LogFactory.getLog(this.getClass());//数据源@Nullableprivate DataSource dataSource;//异常翻译@Nullableprivate volatile SQLExceptionTranslator exceptionTranslator;//懒加载策略private boolean lazyInit = true;public JdbcAccessor() {}public void setDataSource(@Nullable DataSource dataSource) {this.dataSource = dataSource;}@Nullablepublic DataSource getDataSource() {return this.dataSource;}protected DataSource obtainDataSource() {DataSource dataSource = this.getDataSource();Assert.state(dataSource != null, "No DataSource set");return dataSource;}public void setDatabaseProductName(String dbName) {this.exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dbName);}public void setExceptionTranslator(SQLExceptionTranslator exceptionTranslator) {this.exceptionTranslator = exceptionTranslator;}}
复制代码

之所以前面提到spring让我们更方便的处理异常就是这里他包装了一个SQLExceptionTranslator,其他的代码都是做数据源的检查之类的设置数据源,我们看一下其中getExceptionTranslator()方法

    public SQLExceptionTranslator getExceptionTranslator() {SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;if (exceptionTranslator != null) {return exceptionTranslator;} else {synchronized(this) {SQLExceptionTranslator exceptionTranslator = this.exceptionTranslator;if (exceptionTranslator == null) {DataSource dataSource = this.getDataSource();if (dataSource != null) {exceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);} else {exceptionTranslator = new SQLStateSQLExceptionTranslator();}this.exceptionTranslator = (SQLExceptionTranslator)exceptionTranslator;}return (SQLExceptionTranslator)exceptionTranslator;}}}
复制代码

这是一个标准的单例模式,我们在学习模板方法模式的路途中有捕获了一个野生的单例;我们继续看JdbcOperations接口我们调其中一个接口进行解析;

    @Nullable<T> T execute(StatementCallback<T> var1) throws DataAccessException;
复制代码

StatementCallback 接口

    @FunctionalInterfacepublic interface StatementCallback<T> {@NullableT doInStatement(Statement var1) throws SQLException, DataAccessException;}
复制代码

execute实现

    @Nullablepublic <T> T execute(StatementCallback<T> action) throws DataAccessException {//参数检查Assert.notNull(action, "Callback object must not be null");//获取连接Connection con = DataSourceUtils.getConnection(this.obtainDataSource());Statement stmt = null;Object var11;try {//创建一个Statementstmt = con.createStatement();//设置查询超时时间,最大行等参数(就是一开始那些成员变量)this.applyStatementSettings(stmt);//执行回调方法获取结果集T result = action.doInStatement(stmt);//处理警告this.handleWarnings(stmt);var11 = result;} catch (SQLException var9) {//出现错误优雅退出String sql = getSql(action);JdbcUtils.closeStatement(stmt);stmt = null;DataSourceUtils.releaseConnection(con, this.getDataSource());con = null;throw this.translateException("StatementCallback", sql, var9);} finally {JdbcUtils.closeStatement(stmt);DataSourceUtils.releaseConnection(con, this.getDataSource());}return var11;}
复制代码

这一个方法可谓是展现的淋漓尽致,这是一个典型的模板方法+回调模式,我们不需要再写过多的重复代码只需要实现自己获取result的方法就好(StatementCallback)事实上我们自己也不需要实现这个方法,继续向上看,我们是如何调用execute方法的,以查询为例,我们看他是如何一步步调用的:

查询方法

    public List<Users> findAll() {JdbcTemplate jdbcTemplate = DataSourceConfig.getTemplate();String sql = "select nickname,comment,age from users";return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Users>(Users.class));}
复制代码

query实现

    public <T> List<T> query(String sql, RowMapper<T> rowMapper) throws DataAccessException {return (List)result(this.query((String)sql, (ResultSetExtractor)(new RowMapperResultSetExtractor(rowMapper))));}
复制代码

这里的RowMapper是负责将结果集中一行的数据映射成实体返回,用到了反射技术,这里就不展开了,有兴趣的同学可以自己打开源码阅读,继续向下:

query实现

@Nullablepublic <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {Assert.notNull(sql, "SQL must not be null");Assert.notNull(rse, "ResultSetExtractor must not be null");if (this.logger.isDebugEnabled()) {this.logger.debug("Executing SQL query [" + sql + "]");}//实现回调接口class QueryStatementCallback implements StatementCallback<T>, SqlProvider {QueryStatementCallback() {}@Nullablepublic T doInStatement(Statement stmt) throws SQLException {ResultSet rs = null;Object var3;try {//这里真正的执行我们的sql语句rs = stmt.executeQuery(sql);//处理对象映射var3 = rse.extractData(rs);} finally {JdbcUtils.closeResultSet(rs);}return var3;}public String getSql() {return sql;}}//调用execute接口return this.execute((StatementCallback)(new QueryStatementCallback()));}
复制代码

看到这里相信你也不得拍手称奇,Spring处理的非常巧妙,请继续向下看:

update详解

    protected int update(PreparedStatementCreator psc, @Nullable PreparedStatementSetter pss) throws DataAccessException {this.logger.debug("Executing prepared SQL update");return updateCount((Integer)this.execute(psc, (ps) -> {Integer var4;try {if (pss != null) {pss.setValues(ps);}int rows = ps.executeUpdate();if (this.logger.isTraceEnabled()) {this.logger.trace("SQL update affected " + rows + " rows");}var4 = rows;} finally {if (pss instanceof ParameterDisposer) {((ParameterDisposer)pss).cleanupParameters();}}return var4;}));}
复制代码

为什么我要把update函数拎出来讲了,因为update这里使用了lambda函数,回想我们StatementCallback定义只有一个方法的接口,他就是一个函数是接口,所以他是一个函数式接口,所以这里直接使用lambda语法,lambda函数允许你直接内连,为函数接口的抽象方法提供实现,并且整个表达式作为函数接口的一个实例。我们在平时学习中可能知道了lambda语法但是可能使用的较少,或者不知道如何用于实战,那么多阅读源码一定可以提升你的实战能力。 我们可以看到JDBCTemplate使用了很多回调。为什么要用回调(Callback)?如果父类有多个抽象方法,子类需要全部实现这样特别麻烦,而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了就可以完美解决这个问题,可以发现JDBCTemplate并没有完全拘泥于模板方法,非常灵活。我们在实际开发中也可以借鉴这种方法。

模板方法模式的更多应用

事实上很多有关生命周期的类都用到了模板方法模式,最典型的也是可能我们最熟悉的莫过于Servlet了,废话不多说上源码

    public abstract class HttpServlet extends GenericServlet{}
复制代码

HttpServlet的所有方法,我们看到HttpServlet继承了GenericServlet,我们继续看:

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable
{private static final String LSTRING_FILE = "javax.servlet.LocalStrings";private static ResourceBundle lStrings =ResourceBundle.getBundle(LSTRING_FILE);private transient ServletConfig config;public GenericServlet() { }//没有实现钩子public void destroy() {}public String getInitParameter(String name) {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getInitParameter(name);}public Enumeration<String> getInitParameterNames() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getInitParameterNames();}   public ServletConfig getServletConfig() {return config;}public ServletContext getServletContext() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getServletContext();}public String getServletInfo() {return "";}public void init(ServletConfig config) throws ServletException {this.config = config;this.init();}public void init() throws ServletException {}public void log(String msg) {getServletContext().log(getServletName() + ": "+ msg);}public void log(String message, Throwable t) {getServletContext().log(getServletName() + ": " + message, t);}public abstract void service(ServletRequest req, ServletResponse res)throws ServletException, IOException;public String getServletName() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getServletName();}
}
复制代码

可以看到这就是个典型的模板方法类蛮,而且钩子函数也在这里展现的淋漓尽致,如init、destroy方法等,JDK中很多类都是用了模板方法等着你发现哦。

模板方法模式在Vue.js中的应用

模板方法模式在其他语言中也有实现比如Vue.js、React中;比如Vue生命周期肯定使用了模板方法,我就不对源码展开分析了。

总结

设计模式在Spring中得到了大量的应用,感兴趣的同学可以看看Spring源码加以学习,如果你觉得我写的还不错的话点个赞吧,如果你发现了错误,或者不好的地方也可以及时告诉我加以改正,谢谢!您的赞赏和批评是进步路上的好伙伴。

从模板方法模式到JDBCTemplate相关推荐

  1. Java设计模式(代理模式-模板方法模式-命令模式)

    Java设计模式Ⅴ 1.代理模式 1.1 代理模式概述 1.2 静态代理 1.2.1 静态代理概述 1.2.2 代码理解 1.3 动态代理之JDK代理 1.3.1 动态代理之JDK代理概述 1.3.2 ...

  2. 漫说模板方法模式---学生时代的烦恼

    1. 你过了吗?多么令人抓狂的四六级考试呀 有多少人曾经殚精竭虑?有多少人溯夜难眠?就是为了仅仅通过而已. 2. 考试之前,匆忙的需找这2B铅笔,考试之中的答题卡,多么熟悉的过程呀, 多么多的选择题, ...

  3. Mybatis源码阅读之二——模板方法模式与Executor

    [系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...

  4. java设计模式-- 观察者模式 , 外观模式, 模板方法模式

    观察者模式: 观察者模式比较容易理解: 比如天龙八部里面的王语嫣,每次慕容复打架的时候,每当别人将要使出什么绝招时,会提醒慕容复注意提防. 项目中用到的地方就是注册中中心,zookeeper,当Pro ...

  5. 设计模式之模板方法模式(Template Method)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  6. android 模板方法模式,安卓设计模式(七)模板方法模式

    模板方法模式用于固定相关操作的执行流程,将具体实现延迟到子类中 该系列其他文章: 定义: 定义一个操作中算法的框架,而降一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定 ...

  7. JAVA 设计模式 模板方法模式

    定义 模板方法模式 (Template Method) 定义了一个操作中的算法的骨架,而将部分步骤的实现在子类中完成. 模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 模 ...

  8. 模板方法模式与策略模式的区别

    2019独角兽企业重金招聘Python工程师标准>>> 模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以在不改变算法结构的情况下,重新定义 ...

  9. 设计模式--模板方法模式

    2019独角兽企业重金招聘Python工程师标准>>> 模板方法模式(template method): 一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实 ...

最新文章

  1. keras网络变为pytorch网络的一些参考
  2. 图片指定区域根据rgb值计算出对应的坐标地址_【水文】震惊!高考期间,这位学生居然能通过图片隐写实现文字通信!...
  3. wepy公共样式_wepy框架开发小程序文档
  4. 后端技术:Nginx从安装到高可用,看完本篇就够了!
  5. Windows SDK笔记(三):定制控件消息处理函数
  6. 机器阅读理解首次超越人类!云从刷新自然语言处理新纪录
  7. android开发我的新浪微博客户端-用户授权页面功能篇(3.2)
  8. (十七)Activitivi5之组任务分配
  9. python免费领取视频-最经典Python爬虫全套视频免费领,带你从0开始学爬虫
  10. 物理机通过Xshell连接不上虚拟机的解决方案
  11. 云服务平台重构点 @Arthur @Gyb
  12. Android composse 声明式UI,更简单的自定义
  13. [SSL_CHX][2022-1-28]n皇后问题
  14. tomcat的开发模式和生产模式
  15. 2018深信服java笔试题_深信服2018春招笔试题解
  16. 双硬盘安装双系统,配置显卡驱动,安装cuda,pycuda
  17. Allegro如何导入高清Logo、二维码、防静电标识等图片以及汉字
  18. 博士申请 | 澳大利亚麦考瑞大学王岩教授招收机器学习方向全奖博士生
  19. [OHIF-Viewers]医疗数字阅片-医学影像-Module: Panel-自定义面板-中二-Reduxreact-redux状态管理详解...
  20. 图片的居中定位和按钮定位

热门文章

  1. linux运维的发展方向
  2. ContentObserver类的使用
  3. eval解析json注意点
  4. Java File类总结和FileUtils类
  5. android 线程 wait notify
  6. 【APP】squid 3.0反向代理配置
  7. Postfix无法正常发送邮件故障的排除
  8. C#之Action和Func的用法(转自 https://www.cnblogs.com/LipeiNet/p/4694225.html)
  9. 按对象某属性排序的几种方法
  10. 微软Win 7系统安全性方面的十大特色