Builder是Groovy相当有用的一个特性,样例常常用生成XML来展现Builder所带来的便利性,例如要生成下述的XML文档:
xml 代码
- <books amount=‘2‘>
- <description>books to lean groovy<!--</span-->description>
- <book1 name=‘Groovy in Action‘ ISBN=‘1-932394-84-2‘ />
- <book2 name=‘Getting Started with Grails‘ ISBN=‘978-1-4303-0782-2‘ />
- <!--</span-->books>
所使用的Groovy代码是:
import groovy.xml.MarkupBuilder
def xml = new MarkupBuilder()
xml.books(amount:2) {
description ‘books to lean groovy‘
book1 (name:‘Groovy in Action‘, ISBN:‘1-932394-84-2‘)
book2 (name:‘Getting Started with Grails‘, ISBN:‘978-1-4303-0782-2‘)
}
xml.println()
|
从例子可以看到,在groovy中创建xml是相当的便利,你可以写一个相应功能的java程序来对照。实际上这个groovy代码结构跟所生成的html结果十分相似,从某种意义上来说,这像是在使用创建"BOOK XML"的专用的DSL了。
我第一次看到这样代码的时候,心里很是疑惑,这是什么语法,为什么groovy自身的“groovy.xml.MarkupBuilder”类
会有books这个方法?慢慢了解其中的机制后,觉得builder真是groovy精华的集大成者,所谓“解脱之味不独饮”,在此与朋友分享。
1. groovy中是如何调用方法的?
groovy基本上是兼容java语法的,但是为了更加方便开发人员,groovy作了许多便利的改进,比如说方法调用中省略括号
description ‘books to lean groovy‘
等同于
description(‘books to lean groovy‘)
|
所以在java中常用的打印语句
System.out.println("Hello World!");
可以简写为:
println "Hello World!"
|
同时调用方法的时候,传递参数可以加上参数的名字,例如
def method1(String name, int age)
的调用方式可以是:
method1 (age:30, name:"Tom")
|
而groovy方法调用中与我以前所接触语言的最大不同就是 Closure作为参数,而为了方便编写代码,一般也是将Closure写在方法调用的最后,因此上述代码的:
xml.books(amount:2) {
...
}
等同于
xml.books(amount:2, {...})
|
这样一来上述代码的字面意思就明白了,是方法调用中包含Closure,Closure中又包含方法调用的混合体,不过用了groovy的特殊写法。
2. 无中生有的方法
知道上述语句的字面意思后,但还是不知道为什么这些方法调用会成功呢,因为我们的代码并没有定义这些方法。
在groovy中,所有的东西都是对象,而所有的对象都必须实现 GroovyObject接口,该接口定义的方法不多,其中一个是:
Object invokeMethod(String name, Object args)。
原来在groovy中,编译器会将所有的方法调用转换成对invokeMethod的调用,例如:
xml.books(amount:2) {
...
}
会转换成
xml.invokeMethod("books", list of parameters)
|
groovy会将方法中的参数放入一个列表,作为invokeMethod()方法的第二个参数,invokeMethod()缺省实现将调用对象的同名函数,所以平时你对此是没有察觉。
也就是说MarkupBuilder不必事先定义books(), book1()这些方法,它可以假装拥有这些方法,只需要在invokeMethod()的实现中根据name和args的值生成相应的xml代码即可。
实际上,在GroovyObject接口背后还有一个更加重要的 MetaClass,从而使得groovy对象可以在运行时更改对象和类的行为,在groovy中可以轻易做到的有:
1)假装拥有某些方法,这就是MarkupBuilder干的,我觉得这一点在 GPath中发挥的淋漓尽致!
2)在语言级别支持Intercept模式
3)将对自身方法的调用委派给其他对象完成(Delegate模式)
3. 更加灵活的方法名
注意到invodeMethod()的name参数是String类型,我第一个想法就是是否能够用字符串来做方法名呢,例如:
xml."books"(amount:2) {
...
}
|
实验结果证实这样的写法是OK的,而因为groovy中字符串的特性,上述代码还可以这么写:
import groovy.xml.MarkupBuilder
def xml = new MarkupBuilder()
def names = [‘Groovy in Action‘, ‘Getting Started with Grails‘]
def isbns = [‘1-932394-84-2‘, ‘978-1-4303-0782-2‘]
xml.books(amount:names.size()) {
description ‘books to lean groovy‘
for(int i in 0..(names.size()-1)){
def s = "book"+(i+1)
"$s"(name:names[i],ISBN:isbns[i])
}
}
xml.println()
|
我最近在编写一个数独解题程序,就大大享受到字符变量做方法名的好处。因为在解题中往往要对“行/列”两种情况都做一次,代码结构基本一致,只
是多处调用的方法名和属性名不同。在普通Java程序中,要将这样的两段代码合并成一个方法少不了一大堆的if..then语句,而groovy只需要在
开头设置好方法名即可,极为方便。
4. 创建自己的builder
如果你喜欢groovy builder这种模式,也可以创建自己builder,只要你的程序中存在用 Builder模式可以解决的问题,groovy必然能帮上大忙,而且十分简便、优雅。
1)四种形式的createNode方法,groovy会根据你使用的形式自动调用相应的方法
方法名 |
参数形式 |
使用样例 |
createNode |
Object name |
foo() |
createNode |
Object name, Object value
|
foo(‘x‘) |
createNode |
Object name, Map attributes |
foo(a:1) |
createNode |
Object name, Map attributes, Object value |
foo(a:1, ‘x‘) |
2) void setParent(Object parent, Object child)
设置树状继承层次,当你创建子元素的时候,该方法就会被调用
3)void nodeCompleted(Object parent, Object node)
在子元素定义完毕后,该方法也会被自动调用。
例如上述生成xml的代码会被转换成以下的调用方式:
import groovy.xml.MarkupBuilder
def xml = new MarkupBuilder()
def names = [‘Groovy in Action‘, ‘Getting Started with Grails‘]
def isbns = [‘1-932394-84-2‘, ‘978-1-4303-0782-2‘]
def books = xml.createNode(‘books‘, [amount:names.size()])
def des = xml.createNode(‘description‘, ‘books to lean groovy‘)
xml.setParent(books, des)
xml.nodeCompleted(books, des)
for(int i in 0..(names.size()-1)){
def s = "book"+(i+1)
def book = xml.createNode(s, [name:names[i],ISBN:isbns[i]])
xml.setParent(books, book)
xml.nodeCompleted(books, book)
}
xml.nodeCompleted(null, books)
xml.println()
|
在Groovy中令人感兴趣的Builder还有:
1) SwingBuilder和 GroovySWT,用于生成Swing/SWT界面,UI界面用Builder模式来产生是再合适不过的了。不过GroovySWT的发展似乎不如SwingBuilder好,在用户邮件列表中常常提到SwingBuilder,而GroovySWT则好长时间没有被人关注了。
2)属于Grails项目的 Spring Bean Builder,通过Builder来编写Spring的配置文件要比写XML简洁一百倍,而且更重要的是你可以很方便地在运行时动态生成Spring配置。
通过这些精彩例子,相信你不难发现groovy builder能够在你程序中大展身手的地方。
这篇文章就到这里,希望我所描述的能够引发你去了解groovy的兴趣,groovy的确是个好东西!
注:
在本文中还有一个关键的地方没有说明,就是Cloure中的方法调用
{
description ‘books to lean groovy‘
}
|
为什么会触发调用xml.createNode()方法?说明这个问题的主要关键有两个:
1)Closure中变量、方法的作用范围,我们在Closure中能够访问到谁的变量、方法,为什么?
2)Closure是如何将对自身方法的访问“委派”给其他对象的?
如果我够勤奋的话,我会在另一篇blog讨论这两个问题 :)
|