Builder是Groovy相當(dāng)有用的一個(gè)特性,樣例常常用生成XML來(lái)展現(xiàn)Builder所帶來(lái)的便利性,例如要生成下述的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中創(chuàng)建xml是相當(dāng)?shù)谋憷憧梢詫懸粋€(gè)相應(yīng)功能的java程序來(lái)對(duì)照。實(shí)際上這個(gè)groovy代碼結(jié)構(gòu)跟所生成的html結(jié)果十分相似,從某種意義上來(lái)說(shuō),這像是在使用創(chuàng)建"BOOK XML"的專用的DSL了。
我第一次看到這樣代碼的時(shí)候,心里很是疑惑,這是什么語(yǔ)法,為什么groovy自身的“groovy.xml.MarkupBuilder”類
會(huì)有books這個(gè)方法?慢慢了解其中的機(jī)制后,覺(jué)得builder真是groovy精華的集大成者,所謂“解脫之味不獨(dú)飲”,在此與朋友分享。
1. groovy中是如何調(diào)用方法的?
groovy基本上是兼容java語(yǔ)法的,但是為了更加方便開發(fā)人員,groovy作了許多便利的改進(jìn),比如說(shuō)方法調(diào)用中省略括號(hào)
description ‘books to lean groovy‘
等同于
description(‘books to lean groovy‘)
|
所以在java中常用的打印語(yǔ)句
System.out.println("Hello World!");
可以簡(jiǎn)寫為:
println "Hello World!"
|
同時(shí)調(diào)用方法的時(shí)候,傳遞參數(shù)可以加上參數(shù)的名字,例如
def method1(String name, int age)
的調(diào)用方式可以是:
method1 (age:30, name:"Tom")
|
而groovy方法調(diào)用中與我以前所接觸語(yǔ)言的最大不同就是 Closure作為參數(shù),而為了方便編寫代碼,一般也是將Closure寫在方法調(diào)用的最后,因此上述代碼的:
xml.books(amount:2) {
...
}
等同于
xml.books(amount:2, {...})
|
這樣一來(lái)上述代碼的字面意思就明白了,是方法調(diào)用中包含Closure,Closure中又包含方法調(diào)用的混合體,不過(guò)用了groovy的特殊寫法。
2. 無(wú)中生有的方法
知道上述語(yǔ)句的字面意思后,但還是不知道為什么這些方法調(diào)用會(huì)成功呢,因?yàn)槲覀兊拇a并沒(méi)有定義這些方法。
在groovy中,所有的東西都是對(duì)象,而所有的對(duì)象都必須實(shí)現(xiàn) GroovyObject接口,該接口定義的方法不多,其中一個(gè)是:
Object invokeMethod(String name, Object args)。
原來(lái)在groovy中,編譯器會(huì)將所有的方法調(diào)用轉(zhuǎn)換成對(duì)invokeMethod的調(diào)用,例如:
xml.books(amount:2) {
...
}
會(huì)轉(zhuǎn)換成
xml.invokeMethod("books", list of parameters)
|
groovy會(huì)將方法中的參數(shù)放入一個(gè)列表,作為invokeMethod()方法的第二個(gè)參數(shù),invokeMethod()缺省實(shí)現(xiàn)將調(diào)用對(duì)象的同名函數(shù),所以平時(shí)你對(duì)此是沒(méi)有察覺(jué)。
也就是說(shuō)MarkupBuilder不必事先定義books(), book1()這些方法,它可以假裝擁有這些方法,只需要在invokeMethod()的實(shí)現(xiàn)中根據(jù)name和args的值生成相應(yīng)的xml代碼即可。
實(shí)際上,在GroovyObject接口背后還有一個(gè)更加重要的 MetaClass,從而使得groovy對(duì)象可以在運(yùn)行時(shí)更改對(duì)象和類的行為,在groovy中可以輕易做到的有:
1)假裝擁有某些方法,這就是MarkupBuilder干的,我覺(jué)得這一點(diǎn)在 GPath中發(fā)揮的淋漓盡致!
2)在語(yǔ)言級(jí)別支持Intercept模式
3)將對(duì)自身方法的調(diào)用委派給其他對(duì)象完成(Delegate模式)
3. 更加靈活的方法名
注意到invodeMethod()的name參數(shù)是String類型,我第一個(gè)想法就是是否能夠用字符串來(lái)做方法名呢,例如:
xml."books"(amount:2) {
...
}
|
實(shí)驗(yàn)結(jié)果證實(shí)這樣的寫法是OK的,而因?yàn)間roovy中字符串的特性,上述代碼還可以這么寫:
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()
|
我最近在編寫一個(gè)數(shù)獨(dú)解題程序,就大大享受到字符變量做方法名的好處。因?yàn)樵诮忸}中往往要對(duì)“行/列”兩種情況都做一次,代碼結(jié)構(gòu)基本一致,只
是多處調(diào)用的方法名和屬性名不同。在普通Java程序中,要將這樣的兩段代碼合并成一個(gè)方法少不了一大堆的if..then語(yǔ)句,而groovy只需要在
開頭設(shè)置好方法名即可,極為方便。
4. 創(chuàng)建自己的builder
如果你喜歡groovy builder這種模式,也可以創(chuàng)建自己builder,只要你的程序中存在用 Builder模式可以解決的問(wèn)題,groovy必然能幫上大忙,而且十分簡(jiǎn)便、優(yōu)雅。
創(chuàng)建自己的builder也很簡(jiǎn)單,只需要繼承 BuilderSupport類,并實(shí)現(xiàn)其中幾個(gè)抽象方法包括:
1)四種形式的createNode方法,groovy會(huì)根據(jù)你使用的形式自動(dòng)調(diào)用相應(yīng)的方法
方法名 |
參數(shù)形式 |
使用樣例 |
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)
設(shè)置樹狀繼承層次,當(dāng)你創(chuàng)建子元素的時(shí)候,該方法就會(huì)被調(diào)用
3)void nodeCompleted(Object parent, Object node)
在子元素定義完畢后,該方法也會(huì)被自動(dòng)調(diào)用。
例如上述生成xml的代碼會(huì)被轉(zhuǎn)換成以下的調(diào)用方式:
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模式來(lái)產(chǎn)生是再合適不過(guò)的了。不過(guò)GroovySWT的發(fā)展似乎不如SwingBuilder好,在用戶郵件列表中常常提到SwingBuilder,而GroovySWT則好長(zhǎng)時(shí)間沒(méi)有被人關(guān)注了。
2)屬于Grails項(xiàng)目的 Spring Bean Builder,通過(guò)Builder來(lái)編寫Spring的配置文件要比寫XML簡(jiǎn)潔一百倍,而且更重要的是你可以很方便地在運(yùn)行時(shí)動(dòng)態(tài)生成Spring配置。
通過(guò)這些精彩例子,相信你不難發(fā)現(xiàn)groovy builder能夠在你程序中大展身手的地方。
這篇文章就到這里,希望我所描述的能夠引發(fā)你去了解groovy的興趣,groovy的確是個(gè)好東西!
注:
在本文中還有一個(gè)關(guān)鍵的地方?jīng)]有說(shuō)明,就是Cloure中的方法調(diào)用
{
description ‘books to lean groovy‘
}
|
為什么會(huì)觸發(fā)調(diào)用xml.createNode()方法?說(shuō)明這個(gè)問(wèn)題的主要關(guān)鍵有兩個(gè):
1)Closure中變量、方法的作用范圍,我們?cè)贑losure中能夠訪問(wèn)到誰(shuí)的變量、方法,為什么?
2)Closure是如何將對(duì)自身方法的訪問(wèn)“委派”給其他對(duì)象的?
如果我夠勤奮的話,我會(huì)在另一篇blog討論這兩個(gè)問(wèn)題 :)
|