Spring中模板模式和回调模式的讲解

转自:http://www.360doc.com/content/11/0805/13/3617779_138230725.shtml


话回正转,这两天在读spring的jdbc模板,对Spring源码的精妙真是佩服得五体投地,极为经典。
spring中真是集设计模式之大成,而且用得是炉火纯青。模板方法(template method)就在spring中被大量使用,如:jdbcTemplate,hibernateTemplate,JndiTemplate以及一些包围的包装等都无疑使用了模板模式,但spring并不是单纯使用了模板方法,而是在此基础上做了创新,配合callback(回调)一起使用,用得极其灵活。

OK,为了防止文章再被拍砖,我写得更详细点吧,我们首先来回顾一下模板模式:
所谓模板板式,就是在父类中定义算法的主要流程,而把一些个性化的步骤延迟到子类中去实现,父类始终控制着整个流程的主动权,子类只是辅助父类实现某些可定制的步骤。

有些抽象???
好吧,我们用代码来说话吧:
首先,父类要是个抽象类:

Java代码 收藏代码
  1. publicabstractclassTemplatePattern{
  2. //模板方法
  3. publicfinalvoidtemplateMethod(){
  4. method1();
  5. method2();//勾子方法
  6. method3();//抽象方法
  7. }
  8. privatevoidmethod1(){
  9. System.out.println("父类实现业务逻辑");
  10. }
  11. publicvoidmethod2(){
  12. System.out.println("父类默认实现,子类可覆盖");
  13. }
  14. protectedabstractvoidmethod3();//子类负责实现业务逻辑
  15. }


父类中有三个方法,分别是method1(),method2()和method3()。
method1()是私有方法,有且只能由父类实现逻辑,由于方法是private的,所以只能父类调用。
method2()是所谓的 勾子方法。父类提供默认实现,如果子类觉得有必要定制,则可以覆盖父类的默认实现。
method3()是子类必须实现的方法,即制定的步骤。
由此可看出, 算法的流程执行顺序是由父类掌控的,子类只能配合。

下面我们来写第一个子类:
Java代码 收藏代码
  1. publicclassTemplatePatternImplextendsTemplatePattern{
  2. @Override
  3. protectedvoidmethod3(){
  4. System.out.println("method3()在子类TemplatePatternImpl中实现了!!");
  5. }
  6. }

这个子类只覆盖了必须覆盖的方法,我们来测试一下:
Java代码 收藏代码
  1. TemplatePatternt1=newTemplatePatternImpl();
  2. t1.templateMethod();

在控制台中我们可以看到:
Java代码 收藏代码
  1. 父类实现业务逻辑
  2. 父类默认实现,子类可覆盖
  3. method3()在子类TemplatePatternImpl中实现了!!


OK,我们来看看 勾子方法的使用:
定义第2个子类,实现勾子方法:
Java代码 收藏代码
  1. publicclassTemplatePatternImpl2extendsTemplatePattern{
  2. @Override
  3. protectedvoidmethod3(){
  4. System.out.println("method3()在子类TemplatePatternImpl2中实现了!!");
  5. }
  6. /*(non-Javadoc)
  7. *@seecom.jak.pattern.template.example.TemplatePattern#method2()
  8. */
  9. @Override
  10. publicvoidmethod2(){
  11. System.out.println("子类TemplatePatternImpl2覆盖了父类的method2()方法!!");
  12. }
  13. }


来测试一下:
Java代码 收藏代码
  1. TemplatePatternt2=newTemplatePatternImpl2();
  2. t2.templateMethod();

我们看控制台:
Java代码 收藏代码
  1. 父类实现业务逻辑
  2. 子类TemplatePatternImpl2覆盖了父类的method2()方法!!
  3. method3()在子类TemplatePatternImpl2中实现了!!


OK,经典的模板模式回顾完了(大家不要拍砖哦~~~~~~~~~~)

接下来,我们回到正题,自己模仿spring动手写一个基于模板模式和回调的jdbcTemplate。

回顾一下,spring为什么要封装JDBC API,对外提供jdbcTemplate呢(不要仍鸡蛋啊¥·%¥#%)
话说SUN的JDBC API也算是经典了,曾经在某个年代折服了一批人。但随着历史的发展,纯粹的JDBC API已经过于底层,而且不易控制,由开发人员直接接触JDBC API,会造成不可预知的风险。还有,数据连接缓存池的发展,也不可能让开发人员去手工获取JDBC了。

好了,我们来看一段曾经堪称经典的JDBC API代码吧:
Java代码 收藏代码
  1. publicList<User>query(){
  2. List<User>userList=newArrayList<User>();
  3. Stringsql="select*fromUser";
  4. Connectioncon=null;
  5. PreparedStatementpst=null;
  6. ResultSetrs=null;
  7. try{
  8. con=HsqldbUtil.getConnection();
  9. pst=con.prepareStatement(sql);
  10. rs=pst.executeQuery();
  11. Useruser=null;
  12. while(rs.next()){
  13. user=newUser();
  14. user.setId(rs.getInt("id"));
  15. user.setUserName(rs.getString("user_name"));
  16. user.setBirth(rs.getDate("birth"));
  17. user.setCreateDate(rs.getDate("create_date"));
  18. userList.add(user);
  19. }
  20. }catch(SQLExceptione){
  21. e.printStackTrace();
  22. }finally{
  23. if(rs!=null){
  24. try{
  25. rs.close();
  26. }catch(SQLExceptione){
  27. e.printStackTrace();
  28. }
  29. }
  30. try{
  31. pst.close();
  32. }catch(SQLExceptione){
  33. e.printStackTrace();
  34. }
  35. try{
  36. if(!con.isClosed()){
  37. try{
  38. con.close();
  39. }catch(SQLExceptione){
  40. e.printStackTrace();
  41. }
  42. }
  43. }catch(SQLExceptione){
  44. e.printStackTrace();
  45. }
  46. }
  47. returnuserList;
  48. }


上面的代码要若干年前可能是一段十分经典的,还可能被作为example被推广。但时过境迁,倘若哪位程序员现在再在自己的程序中出现以上代码,不是说明该公司的开发框架管理混乱,就说明这位程序员水平太“高”了。
我们试想,一个简单的查询,就要做这么一大堆事情,而且还要处理异常,我们不防来梳理一下:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....
啊~~~~ 我快要晕了,在面向对象编程的年代里,这样的代码简直不能上人容忍。试想,上面我们只是做了一张表的查询,如果我们要做第2张表,第3张表呢,又是一堆重复的代码:
1、获取connection
2、获取statement
3、获取resultset
4、遍历resultset并封装成集合
5、依次关闭connection,statement,resultset,而且还要考虑各种异常
6、.....

这时候, 使用模板模式的时机到了!!!

通过观察我们发现上面步骤中 大多数都是重复的,可复用的,只有在遍历ResultSet并封装成集合的这一步骤是可定制的,因为每张表都映射不同的java bean。这部分代码是没有办法复用的,只能定制。那就让我们用一个抽象的父类把它们封装一下吧:
Java代码 收藏代码
  1. publicabstractclassJdbcTemplate{
  2. //templatemethod
  3. publicfinalObjectexecute(Stringsql)throwsSQLException{
  4. Connectioncon=HsqldbUtil.getConnection();
  5. Statementstmt=null;
  6. try{
  7. stmt=con.createStatement();
  8. ResultSetrs=stmt.executeQuery(sql);
  9. Objectresult=doInStatement(rs);//abstractmethod
  10. returnresult;
  11. }
  12. catch(SQLExceptionex){
  13. ex.printStackTrace();
  14. throwex;
  15. }
  16. finally{
  17. try{
  18. stmt.close();
  19. }catch(SQLExceptione){
  20. e.printStackTrace();
  21. }
  22. try{
  23. if(!con.isClosed()){
  24. try{
  25. con.close();
  26. }catch(SQLExceptione){
  27. e.printStackTrace();
  28. }
  29. }
  30. }catch(SQLExceptione){
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. //implementsinsubclass
  36. protectedabstractObjectdoInStatement(ResultSetrs);
  37. }

在上面这个抽象类中,封装了SUN JDBC API的主要流程,而遍历ResultSet这一步骤则放到抽象方法doInStatement()中,由子类负责实现。
好,我们来定义一个子类,并继承上面的父类:
Java代码 收藏代码
  1. publicclassJdbcTemplateUserImplextendsJdbcTemplate{
  2. @Override
  3. protectedObjectdoInStatement(ResultSetrs){
  4. List<User>userList=newArrayList<User>();
  5. try{
  6. Useruser=null;
  7. while(rs.next()){
  8. user=newUser();
  9. user.setId(rs.getInt("id"));
  10. user.setUserName(rs.getString("user_name"));
  11. user.setBirth(rs.getDate("birth"));
  12. user.setCreateDate(rs.getDate("create_date"));
  13. userList.add(user);
  14. }
  15. returnuserList;
  16. }catch(SQLExceptione){
  17. e.printStackTrace();
  18. returnnull;
  19. }
  20. }
  21. }

由代码可见,我们在doInStatement()方法中,对ResultSet进行了遍历,最后并返回。
有人可能要问:我如何获取ResultSet 并传给doInStatement()方法啊??呵呵,问这个问题的大多是新手。因为此方法不是由子类调用的,而是由父类调用,并把ResultSet传递给子类的。我们来看一下测试代码:
Java代码 收藏代码
  1. Stringsql="select*fromUser";
  2. JdbcTemplatejt=newJdbcTemplateUserImpl();
  3. List<User>userList=(List<User>)jt.execute(sql);


就是这么简单!!

文章至此仿佛告一段落,莫急!不防让我们更深入一些...

试想, 如果我每次用jdbcTemplate时,都要继承一下上面的父类,是不是有些不方面呢?
那就让我们甩掉abstract这顶帽子吧, 这时,就该callback(回调)上场了


所谓回调,就是方法参数中传递一个接口,父类在调用此方法时,必须调用方法中传递的接口的实现类。

那我们就来把上面的代码改造一下,改用回调实现吧:

首先,我们来定义一个回调接口:
Java代码 收藏代码
  1. publicinterfaceStatementCallback{
  2. ObjectdoInStatement(Statementstmt)throwsSQLException;
  3. }


这时候,我们就要方法的签名改一下了:
Java代码 收藏代码
  1. privatefinalObjectexecute(StatementCallbackaction)throwsSQLException


里面的获取数据方式也要做如下修改:
Java代码 收藏代码
  1. Objectresult=action.doInStatement(stmt);//abstractmethod


为了看着顺眼,我们来给他封装一层吧:
Java代码 收藏代码
  1. publicObjectquery(StatementCallbackstmt)throwsSQLException{
  2. returnexecute(stmt);
  3. }


OK,大功告成!
我们来写一个测试类Test.java测试一下吧:
这时候,访问有两种方式,一种是 内部类的方式,一种是 匿名方式

先来看看内部类的方式:
Java代码 收藏代码
  1. //内部类方式
  2. publicObjectquery(finalStringsql)throwsSQLException{
  3. classQueryStatementCallbackimplementsStatementCallback{
  4. publicObjectdoInStatement(Statementstmt)throwsSQLException{
  5. ResultSetrs=stmt.executeQuery(sql);
  6. List<User>userList=newArrayList<User>();
  7. Useruser=null;
  8. while(rs.next()){
  9. user=newUser();
  10. user.setId(rs.getInt("id"));
  11. user.setUserName(rs.getString("user_name"));
  12. user.setBirth(rs.getDate("birth"));
  13. user.setCreateDate(rs.getDate("create_date"));
  14. userList.add(user);
  15. }
  16. returnuserList;
  17. }
  18. }
  19. JdbcTemplatejt=newJdbcTemplate();
  20. returnjt.query(newQueryStatementCallback());
  21. }


在调用jdbcTemplate.query()方法时,传一个StatementCallBack()的实例过去,也就是我们的内部类。

再来看看匿名方式:
Java代码 收藏代码
  1. //匿名类方式
  2. publicObjectquery2(finalStringsql)throwsException{
  3. JdbcTemplatejt=newJdbcTemplate();
  4. returnjt.query(newStatementCallback(){
  5. publicObjectdoInStatement(Statementstmt)throwsSQLException{
  6. ResultSetrs=stmt.executeQuery(sql);
  7. List<User>userList=newArrayList<User>();
  8. Useruser=null;
  9. while(rs.next()){
  10. user=newUser();
  11. user.setId(rs.getInt("id"));
  12. user.setUserName(rs.getString("user_name"));
  13. user.setBirth(rs.getDate("birth"));
  14. user.setCreateDate(rs.getDate("create_date"));
  15. userList.add(user);
  16. }
  17. returnuserList;
  18. }
  19. });
  20. }

相比之下,这种方法更为简洁。
为什么spring不用传统的模板方法,而加之以Callback进行配合呢?
试想,如果父类中有10个抽象方法,而继承它的所有子类则要将这10个抽象方法全部实现,子类显得非常臃肿。而有时候某个子类只需要定制父类中的某一个方法该怎么办呢?这个时候就要用到Callback回调了。

离spring jdbcTemplate再近一点
上面这种方式基本上实现了模板方法+回调模式。但离spring的jdbcTemplate还有些距离。
我们可以再深入一些。。。

我们上面虽然实现了模板方法+回调模式,但相对于Spring的JdbcTemplate则显得有些“丑陋”。Spring引入了RowMapper和ResultSetExtractor的概念。
RowMapper接口负责处理某一行的数据,例如,我们可以在mapRow方法里对某一行记录进行操作,或封装成entity。
ResultSetExtractor是数据集抽取器,负责遍历ResultSet并根据RowMapper里的规则对数据进行处理。
RowMapper和ResultSetExtractor区别是, RowMapper是处理某一行数据,返回一个实体对象。而 ResultSetExtractor是处理一个数据集合,返回一个对象集合

当然,上面所述仅仅是Spring JdbcTemplte实现的基本原理,Spring JdbcTemplate内部还做了更多的事情,比如,把所有的基本操作都封装到JdbcOperations接口内,以及采用JdbcAccessor来管理DataSource和转换异常等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值