Groovy探索之闭包 二

                                  Groovy探索之闭包 二
 
自从编程进入面向对象时代,大家都在孜孜不倦的同面向过程作斗争。想想看,我们大家都爱使用、并且奉为经典的Bang of Four的模式,有多少是针对if…else…这样的语句进行优化的。但是,即使我们做了如上述的大量努力,一些面向过程的语言仍然站在那里嘲笑着我们的无能。
一.解决经典的try…catch问题
不错,try…catch就是我们经常碰到的经典问题。看一看下面的一段代码:
    publicstatic 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()
        thrownew 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代码段进行封装以后,就可以在这个方法中使用它。
publicstatic 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];
           //开始使用shExcel文档进行操作
           ……
    //在最后记得返回一个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)
}
 
怎么样,闭包的使用是不是灵活得多?
 
展开阅读全文

没有更多推荐了,返回首页