# 實戰 Groovy: 關于 MOP 和迷你語言
_Groovy 讓元對象協議從實驗室走進應用程序_
將耳朵貼到地上仔細聽 —— MOP 正在前進!了解一下元對象協議(Meta Object Protocol,MOP)吧,這是一種將應用程序、語言和應用程序構建 _為_語言的翻新方法。
在最近的一次采訪中,Groovy 項目經理 Guillaume Laforge 提到,他最喜歡的 Groovy 特性是它實現了 _元對象協議(Meta Object Protocol)_或稱 MOP。在運行時向一個對象傳遞方法,或者 _消息_時,這個協議使對象可以作出影響它自己的狀態或者行為的特定選擇。正如在 PARC Software Design Area 項目主頁上所描述的(請參閱 [參考資料](#resources)):
> 元對象協議方法……基于這樣一種想法,即人們可以并且應當使語言開放,使用戶可以根據他們的特定需要調整設計和實現。換句話說,鼓勵用戶參與語言設計過程。
顯然,這種大膽的思路提出了構建更智能的應用程序、甚至 _語言_的令人激動的可能性。在本月的專欄中,我將展示 Groovy 是如何實現 MOP 的,然后用一個實際的例子介紹它最令人激動的實際應用:作為一種迷你語言的字典!
在 [下載](#download)一節中提供了字典應用程序示例的源代碼。下面的例子中需要下載 DbUnit,請參閱 [參考資料](#resources)。
## 關于本系列
在自己的開發工作中引入任何工具的關鍵是知道什么時候使用它、什么時候將它放回工具箱。腳本(或者 _動態_)語言會為您的工具箱加入特別強大的工具,前提是在適合的場景正確地使用它。在這方面,_實戰 Groovy_是專門探討 Groovy 的實際使用、并教給您什么時候以及如何成功地使用它們的系列文章。
## 魔術般的 MOP
元對象協議不是 Groovy 獨有的,它也不是由 Groovy 發明者發明的。事實上,它的身世可追溯到 LISP 和 AOP 背后的一些人。考慮到這種身世,MOP 受到 Groovy 的見多識廣的創造者們的歡迎就毫不奇怪了。
在 Groovy 中可以實現 MOP 是因為,在 _Groovyland_中每一個對象都隱式地實現 `groovy.lang.GroovyObject`,它定義了 `invokeMethod()`和 `getProperty()`兩個方法。在運行時,如果向一個對象傳遞消息,而這個對象不是作為類或者其子類中定義的屬性或者方法存在,那么就調用 `getProperty()`或者 `invokeMethod()`方法。
在清單 1 中,我定義了 Groovy 類 `MOPHandler`,它實現了 `invokeMethod()`和 `getProperty()`。當創建了 `MOPHandler`的一個實例后,可以調用任意數量的方法或者屬性,并看到它打印出說明調用了什么的消息。
##### 清單 1\. MOP 得到調用的句柄
```
class MOPHandler {
def invokeMethod(String method, Object params) {
println "MOPHandler was asked to invoke ${method}"
if(params != null){
params.each{ println "\twith parameter ${it}" }
}
}
def getProperty(String property){
println "MOPHandler was asked for property ${property}"
}
}
def hndler = new MOPHandler()
hndler.helloWorld()
hndler.createUser("Joe", 18, new Date())
hndler.name
```
繼續,運行清單 1 中的代碼,就會看到如清單 2 所示的輸出。
##### 清單 2\. 您不相信我,是不是?
```
aglover@glove-ubutu:~/projects/groovy-mop$ groovy
./src/groovy/com/vanward/groovy/MOPHandler1.groovy
MOPHandler was asked to invoke helloWorld
MOPHandler was asked to invoke createUser
with parameter Joe
with parameter 18
with parameter Sun Sep 04 10:32:22 EDT 2005
MOPHandler was asked for property name
```
是不是很不錯? MOP 就像一個安全網,捕捉傳遞錯誤的消息,但是這不是它最妙的功能。用它創建可以以 _一般的方式_智能地對傳遞進來的任何消息作出響應的智能對象,才是它的最大亮點。
* * *
## 讓我成為一種迷你語言
用 MOP 可以做的一件有趣的事情是創建偽領域專用語言,或者稱為 _迷你語言_。這些是專門用于解決特定問題的獨特語言。與像 Java、C# 或者甚至 Groovy 這樣的流行語言不同,它們被看成是用來解決任何問題的一般性語言,而迷你語言只針對特定問題。需要一個例子?請考慮 Unix 及其 shell,如 Bash。
只是為了練習 —— 并且這樣可以真正 _感受_MOP —— 我將在本文的其余部分創建一個字典應用程序,它本身實際就是一個迷你語言。這個應用程序將提供一個查詢字典的界面。它讓用戶可以創建新的單詞項、得到給定單詞的定義、得到給定單詞的同義字、查詢單詞的詞性以及刪除單詞。表 1 總結了這個迷你語言。
##### 表 1\. 字典迷你語言的語義
這個字典應用程序語言有以下語義(按從最特殊到最一般排列):
| 1\. 詞性 | 要查詢單詞的詞性,消息應該以 `is`打頭,后跟這個單詞,然后是 `a`或者 `an`,然后是詞性。 |
| --- | --- |
| 2\. 同義詞 | 要查詢一個單詞的同義詞,消息應該以 `synonymsOf`打頭,然后是這個單詞。 |
| 3\. 刪除單詞 | 要從字典中刪除一個單詞,消息應該以 `remove`或 `delete`打頭,然后是這個單詞。 |
| 4\. 創建單詞 | 要在字典中創建一個新單詞,可將單詞作為方法,并將其定義、詞性和可選的一組同義詞作為參數傳遞。 |
| 5\. 獲取定義 | 要查詢一個單詞的定義,可將這個單詞作為屬性或者方法傳遞。 |
* * *
## 字典應用程序
字典應用程序基于表結構如圖 1 所示的數據庫。如果您經常讀我們的文章,那么您可能會看出這個表結構源自專欄文章“[Mark it up with Groovy Builders](/developerworks/cn/java/j-pg04125/)”。
##### 圖 1\. 字典的簡單數據庫模型

_注意:我將忽略 `definition`的 `EXAMPLE_SENTENCE`這一列。_
我將創建一個簡單的 facade,它將作為用戶與數據庫之間的接口。這個 facade 將通過提供 `invokeMethod`和 `getProperty`的實現來利用 Groovy 的 MOP 特性。`invokeMethod`方法將確定命令并將責任委派給相應的內部 `private`方法。
### 一些前提條件
因為這個應用程序依賴于數據庫,在繼續之前,可能需要復習一下 [GroovySql](/developerworks/cn/java/j-pg01115.html)。我還會使用 Groovy 的正則表達式(在 [感受 Groovy](/developerworks/cn/java/j-alj08034/)中介紹)來確定傳遞給 facade 的消息。
我會在寫這個 facade 時,_用_Groovy 測試它,因此需要回顧 Groovy 的單元測試能力,請參閱我的“[Unit test your Java code faster with Groovy](/developerworks/cn/java/j-pg11094/?ca=dwcn-newsletter-java)”。
最后,在測試過程中,我將用 DbUnit 管理數據庫狀態。因為在本系列中沒有寫過 DbUnit 的內容,我將在繼續之前簡單介紹在 Groovy 中使用 DbUnit。
* * *
## DbUnit,結合 Groovy
DbUnit 是一項 JUnit 擴展,它在每一次運行測試之前,使數據庫處于一種已知狀態。在 Groovy 中使用 DbUnit 非常簡單,因為 DbUnit 提供了一個 API,可以在測試用例中進行委派。為了使用 DbUnit,需要向它提供一個數據庫連接和一個包含作為數據庫種子的文件。將它們插入到 JUnit 的 fixture 機制中(即 `setUp()`)后,就可以繼續了! 清單 3 顯示了字典應用程序的開始測試類。
##### 清單 3\. 字典應用程序的開始測試類
```
package test.com.vanward.groovy
import com.vanward.groovy.SimpleDictionary
import groovy.util.GroovyTestCase
import java.io.File
import java.sql.Connection
import java.sql.DriverManager
import org.dbunit.database.DatabaseConnection
import org.dbunit.database.IDatabaseConnection
import org.dbunit.dataset.IDataSet
import org.dbunit.dataset.xml.FlatXmlDataSet
import org.dbunit.operation.DatabaseOperation
class DictionaryTest extends GroovyTestCase{
def dictionary
void setUp() {
this.handleSetUpOperation()
dictionary = new SimpleDictionary()
}
def handleSetUpOperation() {
def conn = this.getConnection()
def data = this.getDataSet()
try{
DatabaseOperation.CLEAN_INSERT.execute(conn, data)
}finally{
conn.close()
}
}
def getDataSet() {
return new FlatXmlDataSet(new File("test/conf/words-seed.xml"))
}
def getConnection() {
Class.forName("org.gjt.mm.mysql.Driver")
def jdbcConnection = DriverManager.
getConnection("jdbc:mysql://localhost/words",
"words", "words")
return new DatabaseConnection(jdbcConnection)
}
}
```
在清單 3 中,我創建了類 shell,它作為在編寫字典應用程序時的測試類。調用測試時,JUnit 會調用 `setUp()`,它又會調用 DbUnit 的 API。DbUnit 會將在文件 test/conf/words-seed.xml 中找到的數據插入到數據庫中。文件 test/conf/words-seed.xml 的內容可見清單 4。
##### 清單 4\. 示例種子文件
```
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
<word WORD_ID="1" SPELLING="pugnacious" PART_OF_SPEECH="Adjective"/>
<definition DEFINITION_ID="10"
DEFINITION="Combative in nature; belligerent."
WORD_ID="1" />
<synonym SYNONYM_ID="20" WORD_ID="1" SPELLING="belligerent"/>
<synonym SYNONYM_ID="21" WORD_ID="1" SPELLING="aggressive"/>
<word WORD_ID="2" SPELLING="glib" PART_OF_SPEECH="Adjective"/>
<definition DEFINITION_ID="11"
DEFINITION="Performed with a natural, offhand ease"
WORD_ID="2" />
<definition DEFINITION_ID="12"
DEFINITION="Marked by ease and fluency of speech or
writing that often suggests or stems from insincerity,
superficiality, or deceitfulness"
WORD_ID="2" />
<synonym SYNONYM_ID="30" WORD_ID="2" SPELLING="artful"/>
<synonym SYNONYM_ID="31" WORD_ID="2" SPELLING="suave"/>
<synonym SYNONYM_ID="32" WORD_ID="2" SPELLING="insincere"/>
<synonym SYNONYM_ID="33" WORD_ID="2" SPELLING="urbane"/>
<word WORD_ID="3" SPELLING="rowel" PART_OF_SPEECH="Verb"/>
<definition DEFINITION_ID="50"
DEFINITION="to vec, trouble"
WORD_ID="13" />
</dataset>
```
種子文件的 XML 元素(如清單 4 所示)匹配表名。元素的屬性匹配對應的表列。
* * *
## 構建一種迷你語言
定義了測試類后,就可以開始開發(并測試)這個應用程序了。我將按 [表 1](#table1)的排列順序處理每一項特性。
## 正則表達式組
正則表達式組在字典例子中起了主要作用。在 Groovy 中,可以通過 `=~`語法創建一個普通 Java `Matcher`實例。可以用 `Matcher`實例,通過調用 `group()`方法獲得 `String`代碼段。使用括號在一個正則表達式中創建組。例如,正則表達式 `(synonymsOf)(.*)`創建兩個組。一個組完全匹配 `String`_"synonymsOf"_,而另一個匹配 _"synonymsOf"_后面的 _任何_字符。不過要記住,在要獲得組值之前,首先必須調用 `Matcher`的 `matches()`方法。
### 1\. 詞性
如果用戶想要查詢一個單詞的詞性,他或者她可以在消息中傳遞像 `isRowelAVerb`或者 `isEstivalAnAdjective`這樣的內容。(注意,我使用了 camel-case 語義,并通過同時允許 `a`和 `an`以盡量符合正規的英語。)回答問題的智能邏輯就變成了確定正確的單詞,在第一種情況下它是 _Rowel_,而在第二種情況下是 _Estival_。還必須確定詞性,在本例中分別為 _Verb_和 _Adjective_。
使用正則表達式,邏輯就變得簡單了。模式成為 `is(.*)(An|A)(Verb|Adjective|Adverb|Noun)`。我只對第一和第三組感興趣(單詞和詞性)。有了這些,就可以編寫一個簡單的數據庫查詢,以獲取詞性并比較問題和答案,如清單 5 所示。
##### 清單 5\. 確定詞性
```
private determinePartOfSpeech(question){
def matcher = question =~ 'is(.*)(An|A)(Verb|Adjective|Adverb|Noun)'
matcher.matches()
def word = matcher.group(1)
def partOfSpeech = matcher.group(3)
def row = sql.firstRow("select part_of_speech from word
where spelling=?", [word])
return row[0] == partOfSpeech
}
```
清單 5 看起來很簡單,但是我為它編寫了幾個測試用例,看看會是什么情況。
##### 清單 6\. 測試單詞詞性的確定
```
void testPartOfSpeechFalse() {
def val = dictionary.isPugnaciousAVerb()
assertFalse("pugnacious is not a verb", val)
}
void testPartOfSpeechTrue() {
def val = dictionary.isPugnaciousAnAdjective()
assertTrue("pugnacious is an Adjective", val)
}
```
再看看清單 4 中的 XML。因為我用 DbUnit 讓數據庫在每次測試之前處于一種已知狀態,因此可以假定單詞 _pugnacious_是有效的,且其詞性設置為 _Adjective_。在清單 6 中,我在 `DictionaryTest`中加入了兩個簡單的測試(請參閱 [清單 3](#code3)),以確保邏輯正確地工作。
### 2\. 同義詞
查詢同義詞的語義模式如下:`synonymsOfBloviate`,其中所要查的單詞(_Bloviate_)跟在 `synonymsOf`后面。正則表達式更簡單:`(synonymsOf)(.*)`。找到所需要的單詞后,邏輯就進行一個數據庫查詢,該查詢會連接 `word`和 `synonym`表。返回的同義詞加入到一個 `List`中并返回,如清單 7 所示。
##### 清單 7\. getSynonyms 實現
```
private getSynonyms(question){
def matcher = question =~ '(synonymsOf)(.*)'
matcher.matches()
def word = matcher.group(2).toLowerCase()
def syns = []
sql.eachRow("select synonym.spelling from synonym, word " +
"where synonym.word_id = word.word_id and " +
"word.spelling = ?", [word]){ arow ->
syns << arow.spelling
}
return syns
}
```
清單 8 中的測試驗證了,定義了同義詞的單詞可以正確地返回同義詞,而沒有同義詞的單詞(_Rowel_)返回一個空 `List`。
##### 清單 8\. 不要忘記測試這個方法!
```
void testSynonymsForWord() {
def val = dictionary.synonymsOfPugnacious()
def expect = ["belligerent","aggressive"]
assertEquals("should be: " + expect, expect, val)
}
void testNoSynonymsForWord() {
def val = dictionary.synonymsOfRowel()
def expect = []
assertEquals("should be: " + expect, expect, val)
}
```
### 3\. 刪除單詞
清單 9 中的刪除邏輯是靈活的,因為我允許使用 `remove`或者 `delete`后面加單詞的命令。例如,`removeGlib`和 `deleteGlib`都是有效的命令。因此正則表達式變成:`(remove|delete)(.*)`,而邏輯是一個 SQL `delete`。
##### 清單 9\. 刪除單詞
```
private removeWord(word){
def matcher = word =~ '(remove|delete)(.*)'
matcher.matches()
def wordToRemove = matcher.group(2).toLowerCase()
sql.execute("delete from word where spelling=?" , [wordToRemove])
}
```
當然,這種靈活意味著我必須為刪除單詞編寫 _至少_兩個測試用例,如清單 10 所示。要驗證單詞真的被刪除了,我要獲取它們的定義(更多信息請參閱 [5\. 獲取單詞的定義](#retrieve))并確定沒有返回任何東西。
##### 清單 10\. 測試這兩種情況
```
void testDeleteWord() {
dictionary.deleteGlib()
def val = dictionary.glib()
def expect = []
assertEquals("should be: " + expect, expect, val)
}
void testRemoveWord() {
dictionary.removePugnacious()
def val = dictionary.pugnacious()
def expect = []
assertEquals("should be: " + expect, expect, val)
}
```
### 4\. 創建單詞
創建一個新單詞的語義是一個不匹配任何查詢模式并且包含一個參數清單的消息。例如,一個像 `echelon("Noun", ["a level within an organization"])`這樣的消息就符合這種模式。單詞是 _echelon_,第一個參數是詞性,然后是包含定義的 `List`,還有可選的第三個參數,它可以是同義詞的 `List`。
如清單 11 所示,在數據庫中創建一個新單詞就是在正確的表(`word`和 `definition`)中插入這個單詞及其定義,而且,如果有同義詞 `List`的話,那么要把它們都要加入。
##### 清單 11\. 創建一個單詞
```
private createWord(word, defsAndSyms){
def wordId = id++
def definitionId = wordId + 10
sql.execute("insert into word
(word_id, part_of_speech, spelling) values (?, ?, ?)" ,
[wordId, defsAndSyms[0], word])
for(definition in defsAndSyms[1]){
sql.execute("insert into definition
(definition_id, definition, word_id) " +
"values (?, ?, ?)" , [definitionId, definition, wordId])
}
//has a list of synonyms has been passed in
if(defsAndSyms.length > 2){
def synonyms = defsAndSyms[2]
synonyms.each{ syn ->
sql.execute("insert into synonym
(synonym_id, word_id, spelling) values (?, ?, ?)" ,
[id++, wordId, syn])
}
}
}
```
在清單 11 中,主鍵邏輯過于簡單了,不過我可以用幾個測試證明我的信心。
##### 清單 12\. 創建單詞邏輯的測試用例
```
void testCreateWord() {
dictionary.bloviate("Verb",
["To discourse at length in a pompous or boastful manner"],
["orate", "gabble", "lecture"])
def val = dictionary.bloviate()
def expect = "To discourse at length in a pompous or boastful manner"
assertEquals("should be: " + expect, expect, val[0])
}
void testCreateWordNoSynonyms() {
dictionary.echelon("Noun", ["a level within an organization"])
def val = dictionary.echelon()
def expect = "a level within an organization"
assertEquals("should be: " + expect, expect, val[0])
}
```
與通常一樣,我在清單 12 中編寫了幾個測試用例以驗證它按預想的那樣工作。創建單詞后,我查詢這個單詞的 `dictionary`實例以確認它被加入了數據庫。
### 5\. 獲取單詞的定義
任何傳遞給字典應用程序的、不匹配前面任何查詢類型(詞性和同義詞)、并且不包含任何參數的消息都被認為是定義查詢。例如,一個像 `.glib`或者 `.glib()`的消息會返回 _glib_的定義。
##### 清單 13\. 查單詞的定義并不困難!
```
private getDefinitions(word){
def definitions = []
sql.eachRow("select definition.definition from definition, word " +
"where definition.word_Id = word.word_id and " +
"word.spelling = ?", [word]){ arow ->
definitions << arow.definition
}
return definitions
}
```
因為我同時允許讓方法調用和屬性調用作為定義查詢,因此在清單 14 中必須編寫至少兩個測試。就像在清單 6 中一樣,可以假定 _pugnacious_已經在數據庫中了。DbUnit 是不是很方便?
##### 清單 14\. 測試兩種情況 —— 方法調用和屬性
```
void testFindWord() {
def val = dictionary.pugnacious()
def expect = "Combative in nature; belligerent."
assertEquals("should be: " + expect, expect, val[0])
}
void testFindWordAsProperty() {
def val = dictionary.pugnacious
def expect = "Combative in nature; belligerent."
assertEquals("should be: " + expect, expect, val[0])
}
```
這就是字典應用程序的語義。現在讓我們更深入地分析應用程序背后的邏輯。
* * *
## MOP 邏輯
字典應用程序的關鍵是 `invokeMethod`和 `getProperty`的實現。 我要在這些方法中做出智能的決定,即在向應用程序傳遞消息之后,應該如何繼續。決定是通過一組條件語句而確定的,它們進行 `String`操作以確定消息的類型。
清單 15 中 _惟一_麻煩的條件就是第一條,它要驗證消息是一個定義查詢而不是其他可能的組合,如 `is...`、`remove...`、`delete...`或者 `synonymOf...`。
##### 清單 15\. MOP 的核心邏輯
```
def invokeMethod(String methodName, Object params) {
if(isGetDefinitions(methodName, params)){
return getDefinitions(methodName)
}else if (params.length > 0){
createWord(methodName, params)
}else if(methodName[0..1] == 'is'){
return determinePartOfSpeech(methodName)
}else if
(methodName[0..5] == 'remove' || methodName[0..5] == 'delete'){
removeWord(methodName)
}else if (methodName[0..9] == 'synonymsOf'){
return getSynonyms(methodName)
}
}
private isGetDefinitions(methodName, params){
return !(params.length > 0) &&
( (methodName.length() <= 5
&& methodName[0..1] != 'is' ) ||
(methodName.length() <= 10
&& isRemoveDeleteIs(methodName) ) ||
(methodName.length() >= 10
&& methodName[0..9] != 'synonymsOf'
&& isRemoveDeleteIs(methodName)))
}
private isRemoveDeleteIs(methodName){
return (methodName[0..5] != 'remove'
&& methodName[0..5] != 'delete'
&& methodName[0..1] != 'is')
}
```
`isGetDefinitions`方法做了很多工作,其中一些工作依賴于 `isRemoveDeleteIs`。當它確定一個消息不符合定義查詢時,邏輯就變得簡單多了。
清單 16 中的 `getProperty`方法很簡單,因為我規定用戶只能按屬性查詢定義,因此,調用 `getProperty`時,我調用 `getDefinitions`方法。
##### 清單 16\. 太好了,屬性很簡單!
```
def getProperty(String property){
return getDefinitions(property)
}
```
就是這樣了!真的。清單 5 到 16 中的邏輯創建了一個應用程序,它給予用戶相當的靈活性,同時又不會太復雜。當然,如前所述,應用程序中的主鍵邏輯并不是很保險。在這方面有多種改進方法,從數據庫序列到像 Hibernate 這樣的框架都可以,Hibernate 框架用于相當得體地處理 ID。
眼見為實,清單 17 展示了字典應用程序的語言的實際能力。(如果在使用 SAT 之前有這樣的東西該多好!)
##### 清單 17\. 字典展示
```
import com.vanward.groovy.SimpleDictionary
def dict = new SimpleDictionary()
dict.vanward("Adjective", ["Being on, or towards the front"],
["Advanced"])
dict.pulchritude("Noun", ["Great physical beauty and appeal"])
dict.vanward //prints "Being on, or towards the front"
dict.pulchritude() //prints "Great physical beauty and appeal"
dict.synonymsOfVanward() //prints "Advanced"
dict.isVanwardANoun() //prints "false"
dict.removeVanward()
dict.deletePulchritude()
```
* * *
## MOP 的好處
現在,您可能會問 _有什么好處?_我本可以輕易地公開這些 `private`方法,重新安排幾項內容,就可以給出一個正常工作的應用程序(即,可以公開 `getDefinition`方法、`createWord`方法等),而無需使用 MOC。
讓我們分析這種提議 —— 假定我要不使用 Goovy 的 MOC 實現來創建完全相同的字典應用程序。我需要重新定義 `createWord()`方法的參數清單,使它變成類似 `def createWord(word, partOfSpeech, defs, syns=[]){}`的樣子。
可以看出,第一步是去掉 `private`修飾符,用 `def`替換它,這與將方法聲明為 `public`是一樣的。然后,要定義參數,并讓最后一個參數為可選的(`syns=[]`)。
還要定義一個 `delete`方法,它至少要調用 `remove`方法以同時支持 remove 和 delete。詞性查詢也應該修改。可以加入另一個參數,這樣它看起來像 `determinePartOfSpeech(word, partOfSpeech)`這樣。
實際上,經過以上步驟,字典應用程序的概念復雜性就從 MOP 實現轉移到了靜態 API 上了。這就帶來了這個問題 —— _這樣做_的好處是什么?當然您可以看出由于實現 MOP,我為應用程序的使用者提供了無可比擬的靈活性。得到的 API 是一組靜態定義的方法,靈活完全比不上 _語言_本身!
* * *
## 結束語
如果現在頭有些發暈了,那么想一下這個:如果可以構建這樣一個字典應用程序,使用戶可以隨意創建查詢會怎么樣呢?例如,`findAllWordsLikeGlib`或者 `findAllWordsWithSynonymGlib`等等,這些語義對于 MOP 來說這只是文字解析罷了,而對用戶來說,則是強大的查詢功能!
現在再進一步:如果可以創建另一種迷你語言,它對一項業務更有用一些,比如針對股市交易的迷你語言,那會怎么樣呢?如果更進一步,將這個應用程序構建為有友好的控制臺呢?不用編寫腳本,用戶使用的是 Groovy 的 shell。(記住我是怎么說 Bash 的?)
如果您認為這種“煉金術”工具只是癡迷于 Emacs 擴展的古怪的 LISP 家伙們用的,那就錯了!只要看看馬路對面那些喧嚷的 Ruby on Rails 老手們就會知道這種平臺是多么的令人激動。深入了解您就會看到當 MOP 發揮作用時,只要 _遵守命名規范_,不用深入 SQL 就可創建很好的查詢。誰在那兒樂呢?
* * *
## 下載
| 描述 | 名字 | 大小 |
| --- | --- | --- |
| Sample code | [j-groovy-mop.tar.gz](http://www.ibm.com/developerworks/apps/download/index.jsp?contentid=96398&filename=j-groovy-mop.tar.gz&method=http&locale=zh_CN) | 5 KB |
- 實戰 Groovy
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 2 部分
- 實戰 Groovy: SwingBuilder 和 Twitter API,第 1 部分
- 實戰 Groovy: @Delegate 注釋
- 實戰 Groovy: 使用閉包、ExpandoMetaClass 和類別進行元編程
- 實戰 Groovy: 構建和解析 XML
- 實戰 Groovy: for each 剖析
- 實戰 Groovy: Groovy:Java 程序員的 DSL
- 實戰 Groovy: 關于 MOP 和迷你語言
- 實戰 Groovy: 用 curry 過的閉包進行函數式編程
- 實戰 Groovy: Groovy 的騰飛
- 實戰 Groovy: 在 Java 應用程序中加一些 Groovy 進來
- 實戰 Groovy: 用 Groovy 生成器作標記
- 實戰 Groovy: 用 Groovy 打造服務器端
- 實戰 Groovy: 使用 Groovy 模板進行 MVC 編程
- 實戰 Groovy: 用 Groovy 進行 JDBC 編程
- 實戰 Groovy: 用 Groovy 進行 Ant 腳本編程
- 實戰 Groovy: 用 Groovy 更迅速地對 Java 代碼進行單元測試
- alt.lang.jre: 感受 Groovy