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)
}
怎么样,闭包的使用是不是灵活得多?