多くの場合、プログラマが Groovy のような言語に目を向けるのは、ユーティリティーの素早い構築やテストコードの迅速な作成、さらにはより大規模なJava アプリケーションを構成するコンポーネントの作成を行うためです。というのもGroovy には、典型的な Java ベースのシステムが抱えている不要なコード (ノイズ)や複雑性を排除する力があるからです。Groovy の簡潔にして柔軟な構文は、コードのコンパイルに必要でありながらプログラムが本当に成し遂げようとしていることの表現には役に立たない、典型的なJava の構成から開発者を解放してくれます。そのうえ Groovy の緩い型定義は、インターフェースおよびスーパークラス(普通の Java アプリケーションでは種類の異なる具体的な型の間で共通の動作をサポートするために必要)の削減につながり、コードの複雑さが緩和されます。
Groovy によってかつてのプレーンな Java アプリケーションに付随するノイズがどのように除去されるかを示す例として、BruceTate、Justin Ghetland 両氏による著書『開発者ノートシリーズ Spring (参考文献を参照)』(Spring による制御の反転 (Inversion of Control) が紹介されています) のサンプル・コードを利用することにします。それぞれのJava の例を見ていきながら、機能的に等価な Groovy のソースコードと比較していきますが、Groovyによってアプリケーション・コードがどれだけクリアになるのかがすぐにわかると思います。これは、無駄が多くて必ずしもアプリケーションの動作をよく伝えていないJava プログラミングのさまざまな側面を Groovy が切り捨てているからです。
Bruce と Justin の著書の第 1 章では、4 種類のクラスを持つ簡単な自転車ショップ・アプリケーションが作成されています。最初に紹介するのは、Bikeという JavaBean クラスで、これは店内にある 1 台の自転車を表しています。次に、RentABikeという名の自転車ショップ・クラスを取り上げます。このクラスには Bike のコレクションが含まれています。自転車のリストを表示するクラスとしてはCommandLineView という適切な名前の付いたものがあり、これは RentABike に依存しています。さらに、こうした部品を組み合わせて正しく動作するアプリケーションを作成するためのクラスもあります。このクラスは(コードを自ら書くことなくても) RentABike によって完全に設定された CommandLineViewクラスを提供するために Spring を利用しています。
リスト 1 に、1 台の自転車を表すクラスを示します。このクラスは普通の Javaコードによる簡単な JavaBean として実装されており、Java 開発者がこれまでに作成してきた何百というクラスの代表例ともいえるものです。例によって、JavaBeanについて特別に説明すべきことはありません。各プロパティーは private として宣言され、アクセスにはpublic なゲッターとセッターを用います。
リスト 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 は、コンストラクターが 1つとプロパティーが 6 つの小さなサンプルなのですが、ブラウザで表示するとほとんど1 ページが埋まってしまうほどのコードの量です。リスト 2 は、同じ内容の JavaBeanを Groovy で定義したものです。
リスト 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 なフィールドが自動的に定義されるからです。例えばGroovy バージョンのコードの model というプロパティーでは、getModel() およびsetModel() に相当するメソッドが自動的に定義されています。おわかりのとおり、この方法にはクラス内で定義されるプロパティー1 つにつき、メソッド 2 つ分のコードを自分で書かなくてもよくなるというメリットがあります。この点はまた、Groovyにおいて繰り返し取り上げられる、一般的なコーディング規約の簡素化というテーマにも関わってきます。
さらに Groovy では、クラスのデフォルト機能が、(リスト 1 のような) 普通の Java コードでは明示的にコード化しなければならないのに比べると、より簡潔に表現されます。Groovyが真価を発揮するのは、ゲッターやセッター、またはコンストラクターによる何か特別な処理が必要なときです。なぜなら、Groovyのコードはひと目見ただけで関心のある動作の内容がすぐにわかるようになっているからです。例えばリスト2 の場合、setCost() メソッドが cost というプロパティーの値を小数点第 3位に位取りするものであることは容易にわかります。
この控えめな Groovy のコードをリスト 1 の Java コードと比べてみてください。初めてこのリスト 1 を見たときに、こんな特殊な機能がsetCost() メソッドに含まれていたことに気付いたでしょうか。よほど注意して見ていたのでないかぎり、おそらく見過ごしていたはずです。
リスト 3 は Bike クラス用のテストケースですが、ここには自動生成されたアクセサーの使い方が示されています。また、一般的なプログラミング作業をもっと楽にするいう方針に従って、このテストケースではさらに便利なGroovy のドット・プロパティーの名前表記がアクセサーのプロパティーで使われています。そのため、プロパティーmodel は、getModel() とより簡潔な b.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 のコンストラクターのような6 つのプロパティーをすべて受け取るコンストラクターの定義が必要なかったことにも注意してください。さらに、用意したテストケースに対応させるために2 つだけ引数を取る別のコンストラクターを作成する必要もありませんでした。これは、Groovyでは、オブジェクトの作成時にオブジェクトのプロパティーを設定するためのセマンティックによって、さまざまな引数の組み合わせを持った、単に変数を初期化するだけの騒がしくて面倒なコンストラクターが不要になったためです。
先のセクションでは Bike という GroovyBean を取り上げ、Groovy のプロパティーおよびコンストラクターのセマンティックを利用すればソースコードから不要なコード(ノイズ) を除去できることを説明しました。このセクションでは、自転車ショップ・クラスのGroovy バージョンを例にとり、多態性のためのダック・タイピング、コレクション・クラスの強化、演算子のオーバーロード(多重定義) といった別のノイズ除去機能を使うことのメリットを説明します。
Java の自転車ショップ・アプリケーションでは、この自転車ショップがサポートするpublic メソッドを定義するために RentABike というインターフェースを使用します。リスト4 に示すように、RentABike には、自転車 1 台を表す Bike または店内の全 Bikeのリストを返す簡単なメソッドが定義されています。
リスト 4. Java による RentABike インターフェースの定義
import java.util.List;
public interface RentABike {
List getBikes();
Bike getBike(String serialNo);
void setStoreName(String name);
String getStoreName();
}
|
このインターフェースは多態的な動作を可能にするもので、これによって 2 つの重要なシナリオに柔軟性が生まれます。1つは、例えば ArrayList をデータベースに換えるといった実装の変更を決めた場合でも、アプリケーションの残りの部分がこの変更の影響を受けずに済みます。また、インターフェースの利用は単体テストにも柔軟性をもたらします。例えば、アプリケーションでデータベースを使用することにした場合、実際のデータベースには依存しないモック実装クラスをインターフェースから簡単に作成できます。
リスト 5 は、Bike クラスのインスタンスの格納に ArrayList を使った RentABikeインターフェースをプレーンな旧来の Java で実装したものです。
リスト 5. Java による RentABike インターフェースの実装
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 は、Smalltalk や Ruby のような他の動的言語と同様、「ダック・タイピング」による多態性を実行時にサポートしています。ダック・タイピングとは、(アヒルのように振る舞うオブジェクトはアヒルとして扱われるような)オブジェクトの動作によってその扱いが決まるしくみであり、インターフェースなしで多態性をサポートします。Groovyによる ArrayListRentABike の実装では、Java による実装に比べてコードの行数が減っているだけでなく、作成および管理を行うべきモジュールが1 つ減ったことで複雑さも軽減されています。相当な量のノイズ除去だと言えます。
リスト 6 のコードでは、ダック・タイピングだけでなくデフォルトのプロパティー・セマンティックも使われており、ゲッターおよびセッターを持つstoreName と bikes という通常のプロパティーが 2 つ定義されています。これには、リスト1 とリスト 2 で JavaBean と GroovyBean を比較した際に説明したのと同じメリットがあります。さらにリスト 6 には、コードのノイズを除去する Groovy の機能がもう 1 つ示されています。演算子のオーバーロードです。どのようにすればadd() メソッドの代わりに < 演算子が使えるようになるかに注目してください。ネストされたカッコが減ることでコードの可読性が向上しています。ノイズの除去によってコードの可読性を高めるGroovy には、こうした一見何でもない機能が数多く存在します。
each や find のようなクロージャーをメソッドに使用すると、コレクションで非常によく使われるループやサーチなどの処理を簡素化できます。リスト5 に示した getBike() の Java バージョンをリスト 6 の Groovy バージョンと比較してみましょう。Groovyバージョンの場合はちょっとコードを見ただけで、シリアル番号によって Bikeを見つけ出せることがすぐにわかります。一方の Java バージョンにおける Iteratorの定義と次の項目のキャスト処理は、アプリケーションがいったい何をしようとしているのか(実際には自転車の検索ですが) の理解を妨げるノイズといえます。
ここまでは、Java と Groovy の間で自転車 (Bike) クラスと自転車ショップ・クラスの比較を行ってきました。このあたりでそろそろこの自転車ショップ・ビューを細かく見ていくことにしましょう。リスト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());
}
}
}
|
リスト 8 に示す Groovy バージョンをリスト 7 の Java バージョンのビューと比較すれば、Groovyバージョンでは rentaBike プロパティーが def キーワードを使って宣言されていることに気付くでしょう。これは例のダック・タイピングで、このビューを特定の実装に結合していなかったので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
}
}
|
自転車および自転車ショップの各クラスと同じく、Groovy の CommandLineViewにも RentABike プロパティーのために明示的にコーディングされたゲッターやセッターのようなノイズはありません。またprintAllBikes() メソッドでも、コレクション内の各自転車の表示に each を使うことによってコレクションに対するGroovy の強力な拡張機能が利用されています。
ここまでのセクションでは、自転車、自転車ショップ、自転車ショップ・ビューの定義においてGroovy と Java を比較してきました。今度は、作成してきたクラスを使ってアプリケーション全体の組み立てを行います。また、さきほどのコマンドライン・ビューを使って自転車の在庫リストを表示するためにSpring を利用します。
Java プログラミングでは、いったんインターフェースを定義すると、ファクトリー・パターンを使ってその実装クラスを作成する役割をオブジェクト・ファクトリーに委譲することができます。Springはファクトリーとして利用することでかなりのノイズを除去できますし、Groovyと Java のどちらでも機能します。最終的なコードでは Java バージョンでもGroovy バージョンでも、この Spring が CommandLineView クラスのインスタンス生成を担当することになります。
リスト 9 では、CommandLineView のインスタンスを返す前に ArrayList による自転車ショップの実装を作成してCommandLineView に注入するように Spring のコマンド・ライン・ビューが設定されています。このことは、リスト7 とリスト 8 にそれぞれ示した Java バージョンと Groovy バージョンのどちらでもこのArrayList 実装を参照する必要がないことを意味しています。Java バージョンでは、注入されるクラスは必ずといっていいほどインターフェースの実装ではなくインターフェースのほうを参照します。一方のGroovy バージョンでは、def キーワードを使うことでダック・タイピングが利用できます。これで、どちらのバージョンでも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. 自転車ショップ・ビューを作成する Spring 呼び出しの Java バージョン
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();
}
}
|
リスト 10 に示した Java バージョンでは、コマンド・ライン・ビューのインスタンスを要求するためのSpring 呼び出しに、printAllBikes() メソッドをサポートするオブジェクト型へのキャストが必要になります。この場合は、Spring呼び出しで得られるオブジェクトが CommandLineView にキャストされます。
ダック・タイピングをサポートしている Groovy を使えば、キャストは必要ありません。Springによって返されるクラスが適切なメソッド呼び出し (ここでは printAllBikes())に対応できることさえ確認しておけばいいのです。
リスト 11. Spring による組み立ての Groovy バージョン
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 のダック・タイピングは、Spring によるオブジェクトの自動設定をサポートするためのインターフェース宣言を不要にすることでノイズを除去するだけでなく、Springのコンテナによって返される十分に設定が行われた Bean の利用を簡素なものにしています。
本稿では Groovy の威力について説明し、Groovy を利用してどのようにしてソースコードのあり方をすっかり変えられるかを紹介しました。Javaのサンプル・コードに比べ、Groovy のコードがいかにコンパクトで理解しやすいか、おわかりいただけたと思います。Groovyのコードであれば、ベテランの Java アーキテクトから Java 以外の言語を扱うプログラマーに至るまで、誰もがそのコードで何をしようとしているのかを容易に把握できます。動的な型定義をサポートしているGroovy を利用すれば、管理すべきファイルも少なくて済みます。とにかく、Groovyは通常の Java プログラムに付いてまわる相当な量のノイズを除去してくれるのです。本当にこれほどありがたい話はありません。
学ぶために
-
「実用的な Groovy: Groovy を使ってより高速に Java コードをより高速にユニット・テストする」 (Andrew Glover、developerWorks、2004年11月): Groovy による単体テストの作成はこの言語を始めるのにうってつけです。
-
「実用的な Groovy: Java アプリケーションに Groovy を混ぜ込む」 (Andrew Glover、developerWorks、2005年5月): Groovy は簡単に Java アプリケーションに取り込めます。
-
「実用的な Groovy: スムースな演算子」 (Andrew Glover、developerWorks、2005年10月): Groovy を使った演算子のオーバーロード(多重定義) を詳しく紹介しています。
-
「Introduction to Spring using Swing」(Chad Woolley、developerWorks、2005年11月): Spring を使った制御の反転に関するチュートリアルです。
-
「Introduction to Spring 2 and JPA」 (Sing Li、developerWorks、2006年8月) : Spring 2 フレームワークを活用してWeb アプリケーションをスクラッチから順を追って構築していきます。
-
『開発者ノートシリーズ Spring』 (Bruce A. Tate and Justin Gehtland、2005 年 O'Reilly 刊) : Spring を利用してアプリケーションを強化するさまざまな方法を簡潔に紹介しています。
-
「Patterns in action」(Jochen Krebs、developerWorks、2006年5月): 各種のパターンとそれらの関係をよく知らないソフトウェア開発者のために、実際のソフトウェア開発プロジェクトにおけるパターンの使用法を概説しています。
-
Groovy のホームページ: FAQ、IRC チャンネル、メーリングリスト、Wiki など Groovy 初心者向けの多数のリソースが用意されているほか、プロパティー参照用の新しい構文への移行の詳細が記されています。
-
実用的な Groovy シリーズ: Groovy プログラマのためのヒントと手法を開発したシリーズです。
-
developerWorks の Java technology ゾーン: Java プログラミングのあらゆる面を網羅した記事が豊富に用意されています。
製品や技術を入手するために
-
Groovy ダウンロードページ: 同プロジェクト Web サイトから Groovy をダウンロードできます。
-
Spring フレームワーク: 同プロジェクト Web サイトから Spring の最新バージョンをダウンロードできます。
議論するために
-
developerWorks blogs: から developerWorks コミュニティーに加わってください。
Scott Hickey は Bass and Associates の Java コンサルタントです。20 年間にわたるソフトウェア開発の経験を持ち、1998 年から Java に携わっています。Groovy Eclipse Plugin の主任開発者でもあります。