分享

[groovy]通过builder了解groovy的动态性

 chenge 2007-08-06





         

Builder是Groovy相当有用的一个特性,样例常常用生成XML来展现Builder所带来的便利性,例如要生成下述的XML文档:
xml 代码
 
  1. <books amount=‘2‘>  
  2.   <description>books to lean groovy<!--</span-->description>  
  3.   <book1 name=‘Groovy in Action‘ ISBN=‘1-932394-84-2‘ />  
  4.   <book2 name=‘Getting Started with Grails‘ ISBN=‘978-1-4303-0782-2‘ />  
  5. <!--</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的特殊写法。
 
(注:关于groovy中方法调用的更详细说明,请参考Groovy Statmements
 
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必然能帮上大忙,而且十分简便、优雅。
 
创建自己的builder也很简单,只需要继承BuilderSupport类,并实现其中几个抽象方法包括:
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)SwingBuilderGroovySWT,用于生成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讨论这两个问题 :)







    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多