回调机制和模版方法


本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17483273

以前不理解什么叫回调,天天听人家说加一个回调方法啥的,心里想我草,什么叫回调方法啊?然后自己就在网上找啊找啊找,找了很多也不是很明白,现在知道了,所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法,这样子说你是不是有点晕晕的,其实我刚开始也是这样不理解,看了人家说比较经典的回调方式:

  • Class A实现接口CallBack callback——背景1
  • class A中包含一个class B的引用b ——背景2
  • class B有一个参数为callback的方法f(CallBack callback) ——背景3
  • A中的引用b调用B的方法 f(CallBack callback) ——A类调用B类的某个方法 C
  • B中的f(CallBack callback)方法调用A的方法 D——B类调用A类的某个方法D

大家都喜欢用打电话的例子,好吧,为了跟上时代,我也用这个例子好了,我这个例子采用异步加回调

有一天小王遇到一个很难的问题,问题是“1 + 1 = ?”,就打电话问小李,小李一下子也不知道,就跟小王说,等我办完手上的事情,就去想想答案,小王也不会傻傻的拿着电话去等小李的答案吧,于是小王就对小李说,我还要去逛街,你知道了答案就打我电话告诉我,于是挂了电话,自己办自己的事情,过了一个小时,小李打了小王的电话,告诉他答案是2

[java]  view plain  copy
  1. /** 
  2.  * 这是一个回调接口 
  3.  * @author xiaanming 
  4.  * 
  5.  */  
  6. public interface CallBack {  
  7.     /** 
  8.      * 这个是小李知道答案时要调用的函数告诉小王,也就是回调函数 
  9.      * @param result 是答案 
  10.      */  
  11.     public void solve(String result);  
  12. }  

 

[java]  view plain  copy
  1. /** 
  2.  * 这个是小王 
  3.  * @author xiaanming 
  4.  * 实现了一个回调接口CallBack,相当于----->背景一 
  5.  */  
  6. public class Wang implements CallBack {  
  7.     /** 
  8.      * 小李对象的引用 
  9.      * 相当于----->背景二 
  10.      */  
  11.     private Li li;   
  12.   
  13.     /** 
  14.      * 小王的构造方法,持有小李的引用 
  15.      * @param li 
  16.      */  
  17.     public Wang(Li li){  
  18.         this.li = li;  
  19.     }  
  20.       
  21.     /** 
  22.      * 小王通过这个方法去问小李的问题 
  23.      * @param question  就是小王要问的问题,1 + 1 = ? 
  24.      */  
  25.     public void askQuestion(final String question){  
  26.         //这里用一个线程就是异步,  
  27.         new Thread(new Runnable() {  
  28.             @Override  
  29.             public void run() {  
  30.                 /** 
  31.                  * 小王调用小李中的方法,在这里注册回调接口 
  32.                  * 这就相当于A类调用B的方法C 
  33.                  */  
  34.                 li.executeMessage(Wang.this, question);   
  35.             }  
  36.         }).start();  
  37.           
  38.         //小网问完问题挂掉电话就去干其他的事情了,诳街去了  
  39.         play();  
  40.     }  
  41.   
  42.     public void play(){  
  43.         System.out.println("我要逛街去了");  
  44.     }  
  45.   
  46.     /** 
  47.      * 小李知道答案后调用此方法告诉小王,就是所谓的小王的回调方法 
  48.      */  
  49.     @Override  
  50.     public void solve(String result) {  
  51.         System.out.println("小李告诉小王的答案是--->" + result);  
  52.     }  
  53.       
  54. }  


 

[java]  view plain  copy
  1. /** 
  2.  * 这个就是小李啦 
  3.  * @author xiaanming 
  4.  * 
  5.  */  
  6. public class Li {  
  7.     /** 
  8.      * 相当于B类有参数为CallBack callBack的f()---->背景三 
  9.      * @param callBack   
  10.      * @param question  小王问的问题 
  11.      */  
  12.     public void executeMessage(CallBack callBack, String question){  
  13.         System.out.println("小王问的问题--->" + question);  
  14.           
  15.         //模拟小李办自己的事情需要很长时间  
  16.         for(int i=0; i<10000;i++){  
  17.               
  18.         }  
  19.           
  20.         /** 
  21.          * 小李办完自己的事情之后想到了答案是2 
  22.          */  
  23.         String result = "答案是2";  
  24.           
  25.         /** 
  26.          * 于是就打电话告诉小王,调用小王中的方法 
  27.          * 这就相当于B类反过来调用A的方法D 
  28.          */  
  29.         callBack.solve(result);   
  30.   
  31.           
  32.           
  33.     }  
  34.       
  35. }  


 

[java]  view plain  copy
  1. /** 
  2.  * 测试类 
  3.  * @author xiaanming 
  4.  * 
  5.  */  
  6. public class Test {  
  7.     public static void main(String[]args){  
  8.         /** 
  9.          * new 一个小李 
  10.          */  
  11.         Li li = new Li();  
  12.   
  13.         /** 
  14.          * new 一个小王 
  15.          */  
  16.         Wang wang = new Wang(li);  
  17.           
  18.         /** 
  19.          * 小王问小李问题 
  20.          */  
  21.         wang.askQuestion("1 + 1 = ?");  
  22.     }  
  23. }  

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


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

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

模板方法设计模式适用性:  
        1、一次性实现一个算法的不变部分,并将可变的算法留给子类来实现。  
        2、各子类中公共的行为应该被提取出来并集中一个公共父类中以避免代码重复。 
        3、可以控制子类扩展。  

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

Java代码   收藏代码
  1. public abstract class TemplatePattern {  
  2.   
  3.     //模板方法  
  4.     public final void templateMethod(){  
  5.           
  6.         method1();  
  7.         method2();//勾子方法  
  8.         method3();//抽象方法  
  9.     }  
  10.     private void method1(){  
  11.         System.out.println("父类实现业务逻辑");  
  12.     }  
  13.     public void method2(){  
  14.         System.out.println("父类默认实现,子类可覆盖");  
  15.     }  
  16.     protected abstract void method3();//子类负责实现业务逻辑  
  17. }  


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

下面我们来写第一个子类: 
Java代码   收藏代码
  1. public class TemplatePatternImpl extends TemplatePattern {  
  2.   
  3.     @Override  
  4.     protected void method3() {  
  5.         System.out.println("method3()在子类TemplatePatternImpl中实现了!!");  
  6.   
  7.     }  
  8.   
  9. }  

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

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


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


来测试一下: 
Java代码   收藏代码
  1. TemplatePattern t2 = new TemplatePatternImpl2();  
  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. public List<User> query() {  
  2.   
  3.     List<User> userList = new ArrayList<User>();  
  4.     String sql = "select * from User";  
  5.   
  6.     Connection con = null;  
  7.     PreparedStatement pst = null;  
  8.     ResultSet rs = null;  
  9.     try {  
  10.         con = HsqldbUtil.getConnection();  
  11.         pst = con.prepareStatement(sql);  
  12.         rs = pst.executeQuery();  
  13.   
  14.         User user = null;  
  15.         while (rs.next()) {  
  16.   
  17.             user = new User();  
  18.             user.setId(rs.getInt("id"));  
  19.             user.setUserName(rs.getString("user_name"));  
  20.             user.setBirth(rs.getDate("birth"));  
  21.             user.setCreateDate(rs.getDate("create_date"));  
  22.             userList.add(user);  
  23.         }  
  24.   
  25.   
  26.     } catch (SQLException e) {  
  27.         e.printStackTrace();  
  28.     }finally{  
  29.         if(rs != null){  
  30.             try {  
  31.                 rs.close();  
  32.             } catch (SQLException e) {  
  33.                 e.printStackTrace();  
  34.             }  
  35.         }  
  36.         try {  
  37.             pst.close();  
  38.         } catch (SQLException e) {  
  39.             e.printStackTrace();  
  40.         }  
  41.         try {  
  42.             if(!con.isClosed()){  
  43.                 try {  
  44.                     con.close();  
  45.                 } catch (SQLException e) {  
  46.                     e.printStackTrace();  
  47.                 }  
  48.             }  
  49.         } catch (SQLException e) {  
  50.             e.printStackTrace();  
  51.         }  
  52.           
  53.     }  
  54.     return userList;  
  55. }  


上面的代码要若干年前可能是一段十分经典的,还可能被作为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. public abstract class JdbcTemplate {  
  2.   
  3.     //template method  
  4.     public final Object execute(String sql) throws SQLException{  
  5.           
  6.         Connection con = HsqldbUtil.getConnection();  
  7.         Statement stmt = null;  
  8.         try {  
  9.    
  10.             stmt = con.createStatement();  
  11.             ResultSet rs = stmt.executeQuery(sql);  
  12.             Object result = doInStatement(rs);//abstract method   
  13.             return result;  
  14.         }  
  15.         catch (SQLException ex) {  
  16.              ex.printStackTrace();  
  17.              throw ex;  
  18.         }  
  19.         finally {  
  20.    
  21.             try {  
  22.                 stmt.close();  
  23.             } catch (SQLException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.             try {  
  27.                 if(!con.isClosed()){  
  28.                     try {  
  29.                         con.close();  
  30.                     } catch (SQLException e) {  
  31.                         e.printStackTrace();  
  32.                     }  
  33.                 }  
  34.             } catch (SQLException e) {  
  35.                 e.printStackTrace();  
  36.             }  
  37.               
  38.         }  
  39.     }  
  40.       
  41.     //implements in subclass  
  42.     protected abstract Object doInStatement(ResultSet rs);  
  43. }  

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

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


就是这么简单!! 

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

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


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

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

首先,我们来定义一个回调接口: 
Java代码   收藏代码
  1. public interface StatementCallback {  
  2.     Object doInStatement(Statement stmt) throws SQLException;  
  3. }  


这时候,我们就要方法的签名改一下了: 
Java代码   收藏代码
  1. private final Object execute(StatementCallback action) throws SQLException  


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


为了看着顺眼,我们来给他封装一层吧: 
Java代码   收藏代码
  1. public Object query(StatementCallback stmt) throws SQLException{  
  2.     return execute(stmt);  
  3. }  


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

先来看看内部类的方式: 
Java代码   收藏代码
  1. //内部类方式  
  2.  public Object query(final String sql) throws SQLException {  
  3.      class QueryStatementCallback implements StatementCallback {  //相当于Class A实现Callback
  4.    //回调方法,Class A调用通过引用b调用Class B中的f(Callback)方法,而f(Callback)
  5. //方法,可以通过参数Callback调用 Class A类中的D方法.这实现了通过Class B回调Class A中的方法
  6.             public Object doInStatement(Statement stmt) throws SQLException {  
  7.                 ResultSet rs = stmt.executeQuery(sql);  
  8.                 List<User> userList = new ArrayList<User>();  
  9.   
  10.                 User user = null;  
  11.                 while (rs.next()) {  
  12.   
  13.                     user = new User();  
  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.                 return userList;  
  21.   
  22.             }  
  23.   
  24.         }  
  25.   
  26.         JdbcTemplate jt = new JdbcTemplate();    //相当于Class A中有Class B的引用b
  27.         return jt.execute(new QueryStatementCallback());//相当于引用b调用方法f(Callback),
  28. //而exectue方法中有QueryStatementCallback().doInStatement方法。
  29.     }  


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

再来看看匿名方式: 
Java代码   收藏代码
  1. //匿名类方式  
  2.     public Object query2(final String sql) throws Exception{  
  3.           
  4.         JdbcTemplate jt = new JdbcTemplate();  
  5.         return jt.query(new StatementCallback() {  
  6.               
  7.             public Object doInStatement(Statement stmt) throws SQLException {  
  8.                 ResultSet rs = stmt.executeQuery(sql);  
  9.                 List<User> userList = new ArrayList<User>();  
  10.   
  11.                 User user = null;  
  12.                 while (rs.next()) {  
  13.   
  14.                     user = new User();  
  15.                     user.setId(rs.getInt("id"));  
  16.                     user.setUserName(rs.getString("user_name"));  
  17.                     user.setBirth(rs.getDate("birth"));  
  18.                     user.setCreateDate(rs.getDate("create_date"));  
  19.                     userList.add(user);  
  20.                 }  
  21.                 return userList;  
  22.   
  23.             }  
  24.         });  
  25.           
  26.     }  

相比之下,这种方法更为简洁。 
为什么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和转换异常等。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值