Groovy探索之闭包 二

                                  Groovy探索之闭包 二
 
自从编程进入面向对象时代,大家都在孜孜不倦的同面向过程作斗争。想想看,我们大家都爱使用、并且奉为经典的Bang of Four的模式,有多少是针对if…else…这样的语句进行优化的。但是,即使我们做了如上述的大量努力,一些面向过程的语言仍然站在那里嘲笑着我们的无能。
一.解决经典的try…catch问题
不错,try…catch就是我们经常碰到的经典问题。看一看下面的一段代码:
    public static List getDataFromExcel(String fileName, int beginRow, int endRow,ModelIntf model,String[] inputCols,List messages)
{
       List retn = new ArrayList();
       InputStream fs = null ;
       Workbook wb = null ;
       try
       {
           fs = new FileInputStream(fileName);
           wb = Workbook.getWorkbook(fs);
           Sheet sh = wb.getSheet(0);
          
           if ( logger .isDebugEnabled()) {
              logger .debug( "SheetName = " + sh.getName());
           }
          
           int len = inputCols. length ;
          
           String[] values = new String[len];
          
           ……
       }
       catch (Exception e)
       {
           if ( logger .isDebugEnabled()) {
               logger .debug( "ExcelReader" , e);
           }
           e.printStackTrace();
       }
       finally
       {
           try {
              if (fs!= null )
              {
                  fs.close();
              }
              if (wb!= null )
              {
                  wb.close();
              }
           } catch (IOException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
           }
       }
    return retn;
}
 
上面的代码是我从使用JXL API读取Excel文档的一段代码,为了关注我们正在谈论的问题,我将其中一段代码删掉。这样,我们可以清楚的看到代码中的try…catch语句,以及它们所起的作用。
在我的这段读取Excel文档的代码中,有五六个类似这样的方法。它们都要使用这样的try…catch语句,而且它们除了在try语句段里面做的事情不同以外,在catch语句段和finally语句段里做的事情都是一样的。
难道我们都要把相同的catch语句段和finally语句段拷贝到各个方法里去吗?如果需要维护catch语句段和finally语句段的时候,是不是每个方法里都去修改?
看到这里,你一定想起更为经典的JDBC API的使用,并且拿出经典的Spring对JDBC的解决方案:JdbcTemplate。
不错,JdbcTemplate解决方案使用了Java内部类,的确是个不错的解决方案,我们使用起来感觉也相当的不错。但是,我前面在《Groovy探索之闭包 一》中说,Java内部类使用起来相当晦涩和繁琐,实际上并不好用。看到这里,Java的扇子们不要忙着给我扔臭鸡蛋,看看闭包的解决方案先。
下面的代码是我在Grails平台使用Gsql操作存储过程的一段代码,具体用来控制存储过程的事务:
  def doCallWithTransaction(Closure closure) throws DataInTransactionException
 {
      def conn = dataSource.getConnection()
     conn.setAutoCommit( false )
      try
     {
        StoredProcedureDao dao = new StoredProcedureDao(conn)
        
        closure. call (dao)
        
        conn.commit()
     }
      catch (Exception e)
     {
        conn.rollback()
         throw new DataInTransactionException(e)
     }
      finally
     {
        conn.close()
     }
 }
 
可以看到,这段代码除了给出了相同的catch代码段和finally代码段,而将不同的代码执行都交给了一个闭包对象 closure
closure. call (dao)
那么,我们怎么使用这个方法呢?
  //inputList is two or more inputs
  def callWithTransaction(String procName,List inputList) throws DataInTransactionException
 {
      this .doCallWithTransaction{
        dao ->
         inputList. each {
              dao. call (procName,it)
         }
     }
 }
 
看看上面的代码,当使用一个 domain 对象的集成对存储过程进行调用的时候,我们需要考虑该操作的事务。通过调用 doCallWithTransaction ,我们轻松就控制了存储过程的事务。这样的编码的确既简单又直观。
下面我们来看看怎么解决上面的操作 Excel 文档的问题。
//closure 对象包装的是一段代码段,它具体对 Excel 文档进行操作
def getDataFromExcel(Closure closure)
{
       List retn = new ArrayList();
       InputStream fs = null ;
       Workbook wb = null ;
       try
       {
           fs = new FileInputStream(fileName);
           wb = Workbook.getWorkbook(fs);
           Sheet sh = wb.getSheet(0);
          
           if ( logger .isDebugEnabled()) {
              logger .debug( "SheetName = " + sh.getName());
           }
          
       
        // 上面的代码在每一个操作 Excel 的方法中都是相同的,可以照写
 
        // 下面是每一个操作 Excel 的方法中不同的代码,就把它封装在闭包里
        retn = closure. call (sh)
        // 注意:由于闭包封装的代码段需要操作 Sheet 对象,所以必须把 sh 对象传递给闭包对象。
       }
       catch (Exception e)
       {
           if ( logger .isDebugEnabled()) {
              logger .debug( "ExcelReader" , e);
           }
           e.printStackTrace();
       }
       finally
       {
           try {
              if (fs!= null )
              {
                  fs.close();
              }
              if (wb!= null )
              {
                  wb.close();
              }
           } catch (IOException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
           }
       }
    return retn;
}
 
就这样,我们就轻松的完成了对操作 Excel 文档的 try…catch 代码的封装。下面使用这个方法:
// 这是我们最初看到的原始方法,我们使用了闭包对 try…catch 代码段进行封装以后,就可以在这个方法中使用它。
public static List getDataFromExcel(String fileName, int beginRow, int endRow,ModelIntf model,String[] inputCols,List messages)
{
    getDataFromExcel
{
    sh ->
           int len = inputCols. length ;
          
           String[] values = new String[len];
           // 开始使用 sh Excel 文档进行操作
           ……
    // 在最后记得返回一个 List 对象
}
}
这样,我们就可以在该类的四五个与“ getDataFromExcel(String fileName, int beginRow, int endRow,ModelIntf model,String[] inputCols,List messages) ”功能相同的方法中使用上面的闭包了。
 
二.灵活的闭包
闭包不但在解决诸如try…catch这样的面向过程的语句中显得更为简单和易懂;同样,它还有比内部类更为灵活的一面。因为闭包可以定义在代码的很多地方,甚至是一个方法体内,这样使得它方法本身的变量,从而不必需要向闭包对象传递很多的变量。下面试举一例来看看这个问题。
我们在写标签的时候,标签经常有一些必填和非必填的属性。对于非必填的那些属性,如“id”和“alt”等等,我们需要判断它们是否有值。
    def id = attrs[ "id" ]
    if (id!= null &&!(id.trim()== '' ))
    {
       sb. append ( " id=/"" )
       sb. append (id)
       sb. append ( "/"" )
}
 
    def size = attrs[ "size" ]
    if (id!= null &&!(id.trim()== '' ))
    {
       sb. append ( " size=/"" )
       sb. append (size)
       sb. append ( "/"" )
}
遇到了这样的代码,大家都知道使用闭包了。如下:
    def setProperty = {
       propertyName ->
           def propertyValue = attrs[ "${propertyName}" ]
                              if (propertyValue!= null &&!(propertyValue.trim()== '' ))
           {
              sb. append ( " ${propertyName}=/"" )
              sb. append (propertyValue)
              sb. append ( "/"" )
           }
}
 
可以看到,该闭包只能一个输入参数,但实际上还有“attrs”和“sb”两个参数没有通过闭包的输入参数获得。这是因为我们的 setProperty闭包直接定义在该标签实现体内,使得闭包可以直接使用这两个变量。下面是对该闭包的调用:
    def propertyNames = [ 'disabled' , 'size' , 'maxlength' , 'readonly' ,
'tabindex' , 'alt' , 'id' , 'width' , 'height' ,
'onChange' , 'onClick' ]
    propertyNames. each {
           setProperty. call (it)
}
 
怎么样,闭包的使用是不是灵活得多?
 
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值