簡介:Groovy 簡潔的語法將開發人員從那種需要進行代碼編譯但卻無助於表 達 什麼 是程序真正想 要實現的典型的 Java™ 結構中解放了出來。在實戰 Groovy 系列的這一復 興篇中,Groovy 開發 人員兼特約專欄作家 J. Scott Hickey 帶您進行一系列對常規 Java 代碼和 Groovy 代碼的比較,展示 這門令人興奮的語言如何將您解放出來,讓您能夠專注於編碼的重要方面。
通常,程序員們轉而選擇諸如 Groovy 之類的編程語言,是為了構建快速的實 用程序,快速編寫測試 代碼,甚至創建構成大型的 Java 應用程序的組件,而 Groovy 先天具有這樣一 種能力,它能夠減少傳 統的基於 Java 系統所固有的許多冗余並降低其復雜度。Groovy 簡潔而靈活的語 法將開發人員從那種需 要進行代碼編譯卻無助於表達什麼 是程序真正想要實現的典型的 Java 結構中解 放出來。不僅如此, Groovy 輕松的類型通過減少一些接口和超類使代碼不再復雜,這些接口和超類都 是常規 Java 應用程序 用以支持不同具體類型間的通用行為所需的。
為了舉例說明 Groovy 如何減少 Java 應用程序所涉及的無用數據,我將使用 Bruce Tate 和 Justin Ghetland 的 Spring: A Developer's Notebook(參見 參考資料)中的 樣例代碼,該書介紹了 如何使用 Spring 進行控制反轉。每當回顧一個 Java 樣例,我都會將其與實現 相同功能的相應的 Groovy 源代碼進行比較,您將很快發現 Groovy 通過減少 Java 編程的不同方面 (冗余且不必要地傳遞 了應用程序的行為)而使應用程序代碼變得多麼地清晰。
Groovy 之聲
在 Bruce 和 Justin 這本書的第一章中,創建了一個簡單的自行車商店應用 程序,其中包含有四個 類。首先,我將向您展示一個簡單的名為 Bike 的 JavaBean 類,該類代表了一 輛庫存的自行車。然後 ,我會考查自行車商店的類型,名為 RentABike。它包含了一個 Bike 集。還有 一個命名為 CommandLineView 的用於顯示自行車列表的類,該類依賴於 RentABike 類型。最 後,有一個用於集成這 些部分以創建工作應用程序的類,該類利用 Spring 來傳遞完整地配置了 RentABike 類型的 CommandLineView 類 —— 免去了復雜的硬編碼。
停用 JavaBean!
清單 1 中一個代表自行車的類在常規 Java 代碼中被實現為一個簡單的 JavaBean,它是 Java 開發 人員可能已經編寫好的成百上千的類的一個典型。通常來說,JavaBean 並沒有什 麼特殊之處 —— 其屬 性被聲明為 private,且可通過 public getter 和 setter 對其進行訪問。
清單 1. Java 代碼中的 Bike JavaBean
import java.math.BigDecimal;
public class Bike {
private String manufacturer;
private String model;
private int frame;
private String serialNo;
private double weight;
private String status;
private BigDecimal cost;
public Bike(String manufacturer, String model, int frame,
String serialNo, double weight, String status) {
this.manufacturer = manufacturer;
this.model = model;
this.frame = frame;
this.serialNo = serialNo;
this.weight = weight;
this.status = status;
}
public String toString() {
return "com.springbook.Bike : " +
"manufacturer -- " + manufacturer +
"\n: model -- " + model +
"\n: frame -- " + frame +
"\n: serialNo -- " + serialNo +
"\n: weight -- " + weight +
"\n: status -- " + status +
".\n"; }
public String getManufacturer() { return manufacturer; }
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public int getFrame() { return frame; }
public void setFrame(int frame) { this.frame = frame; }
public String getSerialNo() { return serialNo; }
public void setSerialNo(String serialNo) { this.serialNo = serialNo; }
public double getWeight() { return weight; }
public void setWeight(double weight) { this.weight = weight; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public BigDecimal getCost() { return cost; }
public void setCost(BigDecimal cost) {
this.cost = cost.setScale(3,BigDecimal.ROUND_HALF_UP);
}
}
清單 1 是一個只有一個構造方法和六個屬性的小例子,但其代碼卻填滿了浏 覽器的整個頁面!清單 2 顯示了在 Groovy 中定義的相同的 JavaBean:
清單 2. Bike GroovyBean
class Bike {
String manufacturer
String model
Integer frame
String serialNo
Double weight
String status
BigDecimal cost
public void setCost(BigDecimal newCost) {
cost = newCost.setScale(3, BigDecimal.ROUND_HALF_UP)
}
public String toString() {
return """Bike:
manufacturer -- ${manufacturer}
model -- ${model}
frame -- ${frame}
serialNo -- ${serialNo}
"""
}
}
您認為哪一個沒那麼多冗余呢?
Groovy 版的代碼要少很多很多,這是因為 Groovy 的默認屬性語義用 public 訪問器和存取器自動 定義了 private 域。例如,上述 model 屬性現在有了自動定義的 getModel() 方法和 setModel() 方 法。可以看到,這項技術的好處是,不必在一種類型中按照屬性手工定義兩個方 法!這也解釋了 Groovy 中的一條反復強調的定律:使普通的編碼規則變得簡單。
另外,在 Groovy 中,類的默認函數表達得更為簡潔,而在常規 Java 代碼( 如 清單 1)中該函數 必須顯式編碼。當需要用構造函數或 getter 或 setter 來完成一些特殊任務時 ,Groovy 真的很出色, 因為只需瞥一眼代碼,其精彩的行為就會立即變得十分明顯。例如,在 清單 2 中很容易看出, setCost() 方法會將 cost 屬性換算為三個十進制的位。
將這段不太顯眼的 Groovy 代碼同 清單 1 中的 Java 源代碼進行比較。第一 次閱讀這段代碼時,您 注意到 setCost() 方法中嵌入了特殊的函數了嗎?除非仔細觀察,否則太容易看 漏了!
Groovy 測試
清單 3 中 Bike 類的測試用例展示了如何使用自動生成的訪問器。同時,出 於進一步簡化通用編程 任務的考慮,測試用例也使用了更為簡便的 Groovy 點屬性名 標記來訪問屬性。 相應地,能夠通過 getModel() 方法或更為簡潔的 b.model 形式來引用 model 屬性。
清單 3. Groovy 中的 Bike 測試用例
class BikeTest extends GroovyTestCase {
void testBike() {
// Groovy way to initialize a new object
def b = new Bike(manufacturer:"Shimano", model:"Roadmaster")
// explicitly call the default accessors
assert b.getManufacturer() == "Shimano"
assert b.getModel() == "Roadmaster"
// Groovier way to invoke accessors
assert b.model == "Roadmaster"
assert b.manufacturer == "Shimano"
}
}
也注意到在上述 Groovy 例子中,不必定義一個如 清單 1 中定義的 Java 構 造函數那樣的能夠接受 全部六個屬性的構造函數。同時,也不必要創建另一個 只含兩個參數的構造函數 來支持測試用例 —— 在對象創建過程中設置對象屬性的 Groovy 的語義不需要這種冗余、煩人的構造 函數(它們綜合有多個 參數但其作用卻只是初始化變量)。
降低的復雜性
在前面的部分中,Bike GroovyBean 利用了 Groovy 的屬性和構造語義以減少 源代碼中的冗余。在這 一部分中,Groovy 版的自行車商店也將受益於額外的冗余減少特性,如針對多態 的 duck-typing、集合 類的改進及操作符重載。
Grooving 中使用多態
在 Java 自行車應用程序中,名為 RentABike 的接口是用來定義由自行車商 店支持的 public 方法 的。正如在清單 4 中說明的那樣,RentABike 定義了一些簡單的方法,這些方法 用來返回商店中單個的 Bike 或所有 Bike 的列表。
清單 4. 由 Java 定義的 RentABike 接口
import java.util.List;
public interface RentABike {
List getBikes();
Bike getBike(String serialNo);
void setStoreName(String name);
String getStoreName();
}
此接口允許多態行為並在下面兩種重要情況下提供了靈活性。其一,如果要決 定將實現由 ArrayList 轉變為數據庫形式,余下的應用程序與該變化是隔絕的。其二,使用接口為單元 測試提供靈活性。例如 ,如果要決定為應用程序使用數據庫,可以輕易地創建一個該類型的模擬實現, 而且它不依賴於實時數 據庫。
清單 5 是 RentABike 接口的 Java 實現,它使用 ArrayList 來存儲多個 Bike 類:
清單 5. RentABike 接口的 Java 實現
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListRentABike implements RentABike {
private String storeName;
final List bikes = new ArrayList();
public void setStoreName(String name) {
this.storeName = name;
}
public String getStoreName() {
return storeName;
}
public ArrayListRentABike(String storeName) {
this.storeName = storeName;
bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair"));
bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222",12, "Excellent"));
bikes.add(new Bike("Trek","6000", 19, "33333", 12.4,"Fair"));
}
public String toString() { return "com.springbook.RentABike: " + storeName; }
public List getBikes() { return bikes; }
public Bike getBike(String serialNo) {
Iterator iter = bikes.iterator();
while(iter.hasNext()) {
Bike bike = (Bike)iter.next();
if(serialNo.equals(bike.getSerialNo())) return bike;
}
return null;
}
}
現在將 清單 4 和 5 中的 Java 代碼同 清單 6 中的 Groovy 代碼進行比較 。Groovy 版的代碼很靈 巧地避免了對 RentABike 接口的需求。
清單 6. Groovy 的 ArrayListRentABike 實現
public class ArrayListRentABike {
String storeName
List bikes = []
public ArrayListRentABike(){
// add new instances of Bike using Groovy's initializer syntax
bikes << new Bike(manufacturer:"Shimano", model:"Roadmaster",
frame: 20, serialNo:"11111", weight:15, status:"Fair")
bikes << new Bike(manufacturer:"Cannondale", model:"F2000",
frame: 18, serialNo:"22222", weight:12, status:"Excellent")
bikes << new Bike(manufacturer:"Trek", model:"6000",
frame: 19, serialNo:"33333", weight:12.4, status:"Fair")
}
// Groovy returns the last value if no return statement is specified
public String toString() { "Store Name:=" + storeName }
// Find a bike by the serial number
def getBike(serialNo) { bikes.find{it.serialNo == serialNo} }
}
Groovy 方式下的和諧集合
用諸如 each 和 find 的方法來使用閉包簡化了最常見的任務集,如循環和查 找。將 清單 5 中 Java 版本的 getBike() 同 清單 6 中 Groovy 版的進行比較 。在 Groovy 中,很明顯是通過其序列號來尋找 Bike。而在 Java 版中,定義了 一個 Iterator 並計算列表中下一個條目,這很多余,且不利於理解該應用程序 真正要實現的功能,即尋找一輛自行車。
Groovy 像其他動態語言(如 Smalltalk 或 Ruby)一樣支持具有 “duck typing” 的多態 —— 在 運行時,如果一個對象表現得像個 duck ,它就會被視為 duck ,從而支持無 接 口的多態。有了 Groovy ArrayListRentABike 實現,不但減少了成行的代碼,而且由於少創建和 維護一個模塊,復雜性 也降低了。那是非常重要的冗余減少!
除了 duck typing,清單 6 中的默認屬性語法還簡單地定義了兩個普通屬性 ,storeName 和 bikes ,如同擁有了 getter 和 setter 一樣。這樣做的好處和在 清單 1 和 2 中比較 JavaBean-GroovyBean 時所說明的好處是一樣的。尤其是,清單 6 還闡明了另一個用以減少代碼冗余的 Groovy 特性 —— 操 作符重載。請注意如何使用 << 操作符來代替 add() 方法。通過減少一層 嵌套的括號使代碼的可 讀性得以改善。這也是 Groovy 眾多通過減少冗余而改善代碼可讀性的特性中的 一種。
透明的代碼
Groovy 中的 duck-typing 和屬性語義通過減少代碼行數來減少冗余;然而, 也可以通過增加透明度 來減少冗余。在 清單 6 中,請注意在 ArrayListRentABike 構造函數中創建新 Bike 對象的方式。 Groovy 名稱和值的初始化語法比 Java 版的略微詳細,但這些額外的代碼卻使整 個代碼更為透明 —— 將這一點與 清單 5 中 Java 版的進行比較,哪個屬性被初始化為哪個值會立即 明顯 起來。不回過頭來 看 Bike JavaBean 源代碼,您能記起哪個參數是 frame,哪個是 new Bike ("Shimano"、 "Roadmaster" 、20、 "11111"、15、 "Fair") 的 weight 嗎?盡管我剛寫過,但我還是記不起 來!
一個更小的、更加 Groovy 化的自行車商店視圖
到目前為止,我將 Bike 和自行車商店類型在 Java 和 Groovy 下進行了比較 。現在,到了更近距離 地看一下自行車商店的視圖 的時候了。在清單 7 中,該視圖類具有一個 rentaBike 屬性,該屬性引用 RentABike 接口並在行動上說明 Java 版的多態。由於 Java 要求所有類屬性都 必須是聲明過的類型, 而不是針對某個特定的實現進行編碼,我向一個接口編程,該接口使這個類跟 RentABike 實現的改變分 隔開來。這是很好的、扎實的 Java 編程實踐。
清單 7. Java 版的自行車商店視圖
public class CommandLineView {
private RentABike rentaBike;
public CommandLineView() {}
public void setRentaBike(RentABike rentaBike) {
this.rentaBike = rentaBike;
}
public RentABike getRentaBike() { return this.rentaBike; }
public void printAllBikes() {
System.out.println(rentaBike.toString());
Iterator iter = rentaBike.getBikes().iterator();
while(iter.hasNext()) {
Bike bike = (Bike)iter.next();
System.out.println(bike.toString());
}
}
}
將清單 7 中的 Java 視圖與清單 8 中的 Groovy 視圖進行比較,請注意我聲 明了帶 def 關鍵字的 rentaBike。這是 duck-typing 的實踐,與 Java 版的很 像。我正在實踐好的軟件設計,這是因為我還沒有將視圖和特定的實現耦合起來 。但我也能夠不 定義接口就實現解耦。
清單 8. Groovy 的 CommandLineView
public class CommandLineView {
def rentaBike // no interface or concrete type required, duck typing in action
def printAllBikes() {
println rentaBike
rentaBike.bikes.each{ println it} // no iterators or casting
}
}
與 Bike 和自行車商店類型一樣,Groovy 的 CommandLineView 沒有了為 RentABike 屬性所顯式編寫 的 getter 或 setter 的冗余。同樣,在 printAllBikes() 方法中,通過使用 each 來打印在集合裡找到的每輛自行車, 我再一次利用了 Groovy 強大的集合功能的改進。
使用 Spring 進行組裝
在前面的部分中,已經介紹了 Groovy 相比 Java 是如何定義自行車、自行車 商店和自行車商店視圖的。現在該介紹如何將整個應用程序組裝起來並在命令行 視圖中使用 Spring 來顯示庫存自行車列表了。
工廠的工廠
在 Java 編程中,一旦定義了一個接口,就可以使用工廠模式將創建真實的實 現類的責任委派給一個對象工廠。使用 Spring 作為一個工廠極大地減少了冗余 ,並在 Groovy 和 Java 中都能夠使用,在最終的代碼樣例中,Spring 負責在 Java 和 Groovy 中創建一個 CommandLineView 類型的實例。
在清單 9 中,配置 Spring 是為了在返回一個 CommandLineView 實例前,創 建並將自行車商店的 ArrayList 實現注入 CommandLineView 中。這意味著,不 需要引用在 清單 7 和 8 的 Java 或是 Groovy 版的命令行視圖中的 ArrayList 實現。在 Java 版中,被注入的類通常都會引用一個接口而不是實現。在 Groovy 中,由於使用 def 關鍵字,而允許利用 duck-typing。無論在哪個實例中,配置 Spring 的目的都是為了將自行車商店視圖的實例和自行車商店類型的實例完整地 配置起來!
清單 9. Spring 配置文件
<beans>
<bean id="rentaBike" class="ArrayListRentABike">
<property name="storeName"><value>"Bruce's Bikes (spring bean)"</value></property>
</bean>
<bean id="commandLineView" class="CommandLineView">
<property name="rentaBike"><ref bean="rentaBike"/></property>
</bean>
</beans>
在清單 10 和 11 中,自行車商店組裝類型用清單 9 中的配置文件創建了一 個 Spring 的 ClassPathXmlApplicationContext 實例,然後,請求自行車商店 視圖的實例:
清單 10. Java 版本下調用 Spring 創建自行車商店視圖
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class RentABikeAssembler {
public static final void main(String[] args) {
// Create a Spring application context object
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("RentABike-context.xml");
// retrieve an object from Spring and cast to a specific type
CommandLineView clv = (CommandLineView)ctx.getBean("commandLineView");
clv.printAllBikes();
}
}
請注意Java 版的清單 10 中,用以請求一個命令行視圖實例的對 Spring 的 調用要求向一個支持 printAllBikes() 方法的對象類型強制轉換。在本例中,由 Spring 導出的對象將被強制轉換為 CommandLineView。
有了 Groovy 及其對 duck-typing 的支持,將不再需要強制轉換。只需確保 由 Spring 返回的類能夠對合適的方法調用(printAllBikes())作出響應。
清單 11. Groovy 版的 Spring 組合件
import org.springframework.context.support.ClassPathXmlApplicationContext
class RentABikeAssembler {
public static final void main(String[] args) {
// Create a Spring application context object
def ctx = new ClassPathXmlApplicationContext("RentABike-context.xml")
//Ask Spring for an instance of CommandLineView, with a
//Bike store implementation set by Spring
def clv = ctx.getBean("commandLineView")
//duck typing again
clv.printAllBikes()
}
}
正如在清單 11 中看到的那樣,在 Groovy 中,duck-typing 對減少冗余的貢 獻不僅體現在無需聲明接口即可支持由 Spring 自動配置對象,其貢獻還體現在 簡化了對完全配置的 bean 的使用(一旦它從 Spring 容器中返回)。
與 Groovy 相協調
至此,希望我已經闡明了 Groovy 的強大功能及其如何能如此深遠地改變源代 碼的性質。與上述 Java 樣例相比,Groovy 代碼更簡短也更易理解。任何人,無 論是經驗豐富的 Java 架構師還是非 Java 程序員,都能輕易地掌握 Groovy 代 碼的意圖。Groovy 及其對動態類型的支持減少了要管理的文件。總之,使用 Groovy 減少了在典型的 Java 程序中所常見的大量冗余。這實在是福音啊!
來源:
http://www.ibm.com/developerworks/cn/java/j-pg09196.html