Guiceを使ってみる
Guice(ジュースと読む)は、googleが作成したJava製のDIコンテナです。Guiceはオープンソースであり、無償で利用可能です。
以下のURLでGuice本体やドキュメント(PDF)などがダウンロードできます。
Guice
http://code.google.com/p/google-guice/
面白そうなので、ちょっとさわってみました。
Guiceの特徴
Guiceの特徴としては、メタデータの設定にXMLファイルを使用しないところです。なので、DI対象となるBeanの設定はXMLで行いません。代わりにアノテーションなどで設定を行います。あくまでXMLではなく、Javaコードで設定を行うわけです。
使用例1
Guiceの使用方法は以下のようになります。
まず、DI対象となるクラスを作成します。
public class Service { public void go() { System.out.println("done!"); } }
次に上記クラスを利用するクラスです。(DIコンテナ使用)
public class Client { public static void main(String[] args) { // DIコンテナを取得 Injector injector = Guice.createInjector(); // DIコンテナから欲しいインスタンスを取得 Service service = injector.getInstance(Service.class); // インスタンスからメソッド実行 service.go(); } }
上記のようにInjector.getInstanceメソッドの引数に欲しいインスタンスの型を渡すことで、その型のインスタンスを取得することができます。おそらくこれが最も基本的な使用パターンでしょう。
使用例2
Guice(DIコンテナ)から取得したインスタンスは、Guiceによって管理されます。Guice管理下にあるインスタンスが別のインスタンスを参照している場合、この別のインスタンスもGuiceの管理対象となるので、DIすることができます。例を見てみましょう。
1つ目のクラス(2つ目のクラスを使用するように変更)
public class Service { // DI対象であることを定義 @Inject private Service2 service2; public void go() { service2.go(); } }
2つ目のクラス(新規作成)
public class Service2 { public void go() { System.out.println("done!"); } }
クライアントクラス
DIでServiceのインスタンスを取得します。
public class Client { public static void main(String[] args) { // DIコンテナを取得 Injector injector = Guice.createInjector(); // DIコンテナから欲しいインスタンスを取得 Service service = injector.getInstance(Service.class); // インスタンスからメソッド実行 service.go(); } }
ServiceはService2をnewせずに使用しています。このため、本来ならばService2使用時にNullPointerExceptionが発生します。
しかし、ServiceはGuiceの管理下におかれているので、Service2もGuice管理下となり、必要なとき(Service2のメソッド実行時)にGuiceによってインスタンスが生成されます。このため、NullPointerExceptionは発生しません。
使用例3
これまでの例は、通常のクラスを使用したものでした。しかしDIでは通常、インタフェースを駆使してオブジェクトが疎結合になるようにします。ここではこの例について見ていきます。
まずインタフェースです。
public interface Service { public void go(); }
ここで試しに、上記のインタフェースクラスを指定してDIしてみましょう。
public class Client { public static void main(String[] args) { Injector injector = Guice.createInjector(); Service service = injector.getInstance(Service.class); service.go(); } }
上記のコードは正常に動作せず、例外が発生します。
Exception in thread "main" com.google.inject.ConfigurationException: Missing binding to quice.sample.service.Service. at com.google.inject.InjectorImpl.getProvider(InjectorImpl.java:696) at com.google.inject.InjectorImpl.getProvider(InjectorImpl.java:689) at com.google.inject.InjectorImpl.getInstance(InjectorImpl.java:728) at quice.sample.main.Client.main(Client.java:15)
理由は単純で、インタフェース型を指定したときにどの実装クラスをインスタンス化すれば良いのか、Guiceはわからないためです。このため、インタフェース型を指定したときに、実際にどの実装クラスをバインドすればよいのかをGuiceに教えてあげる必要があります。
インタフェース実装クラス
インタフェースを指定したときに実際にバインドされる実装クラスを作成します。
public class ServiceImpl implements Service { public void go() { System.out.println("done!"); } }
バインドの定義は、Guiceの「Module」インタフェース実装クラスを作成して行います。
public class MyModule implements Module { public void configure(Binder binder) { binder.bind(Service.class).to(ServiceImpl.class); } }
Moduleインタフェースはconfigureメソッドを定義しているので、これを実装します。
上記は、Service(インタフェース)を指定すると、ServiceImpl(実装クラス)にバインドされることを定義しています。
クライアントクラス
DIコンテナを取得する際、作成したModule実装クラスを引数に与えて、Guiceにバインドの設定を教えてあげます。
public class Client { public static void main(String[] args) { // Module実装クラスをcreateInjectorメソッドの引数に渡す // これにより、DIコンテナにバインドの設定が適用される MyModule module = new MyModule(); Injector injector = Guice.createInjector(module); Service service = injector.getInstance(Service.class); // 取得したインスタンスのクラスを表示してみる System.out.println(service.getClass()); service.go(); } }
で、実行結果です。
class quice.sample.service.ServiceImpl done!
ServiceImplクラスのインスタンスが取得され、正常にコードが実行されたことが分かりますね。
ちなみに、Module実装クラスで以下のコードを記述して実行すると例外が発生します。
public class MyModule implements Module { public void configure(Binder binder) { binder.bind(Service.class).to(ServiceImpl.class); binder.bind(Service.class).to(ServiceImpl2.class); } }
※ServiceImpl2はServiceImplと同様、Serviceインタフェースの実装クラスだとします。
実行結果は、
Exception in thread "main" com.google.inject.CreationException: Guice configuration errors: 1) Error at guice.sample.module.MyModule.configure(MyModule.java:14): A binding to quice.sample.service.Service was already configured at guice.sample.module.MyModule.configure(MyModule.java:13). 1 error[s] at com.google.inject.BinderImpl.createInjector(BinderImpl.java:277) at com.google.inject.Guice.createInjector(Guice.java:79) at com.google.inject.Guice.createInjector(Guice.java:53) at com.google.inject.Guice.createInjector(Guice.java:43) at quice.sample.main.Client.main(Client.java:17)
バインドする実装クラスはすでに設定されてますよ、ということのようです。
ここまで紹介してきた使用方法では、一つのインタフェースに対して、複数の実装クラスがあった場合に対処できません。
でも、当然のごとく、対処する方法がGuiceには用意されています。これについてはまた後日。
Rubyのブロック構文
Rubyは、「ブロック構文」という機能を標準で備えています。これを使うと、AOPに類似した機能を実現できます。
実際に例を見てみましょう。
まず、「yield」というキーワードを記述したメソッドを定義します。
def testblock puts "begin operation"; yield puts "end operation"; end
「yield」の前後で文字列を出力するだけのメソッドです。
次に、上記で定義したメソッドを利用するコードを記述します。
testblock { puts "method execution!"; }
実行結果は以下のようになります。
begin operation method execution! end operation
しくみを簡単に説明すると、以下のようになります。
- testblockメソッドの利用者側は、{ }でくくられた「プログラムコード」を引数としています。*1
- testblockメソッド実行時には、「yield」の部分が引数のコードに置換され、実行されます。
つまり、実行時には以下のようなコードが実行されます。
def testblock puts "begin operation"; puts "method execution!"; # { }で引数として設定したコード puts "end operation"; end
通常、メソッドの引数に指定できるのは変数 or 定数ですが、Rubyのブロック構文では「プログラムコード」を引数にできるわけです。ブロック構文を使用することで、たとえば以下のような前処理/後処理を必要とする処理を自動化できます。
- ファイルのopen/close
- DB接続時のコネクションのopen/close
- トランザクションのbegin/end
Rubyはこういう便利な機能が標準で提供されているのが魅力的ですね。
*1: { }の代わりにdo-endが使用可能です。
commonsのToStringBuilderを使う
プログラムのある時点で変数の値を表示するとき、commons-langのToStringBuilderを使用すると良い感じです。
準備
以下のサイトからcommons-langのjarファイルをダウンロードします。
http://jakarta.apache.org/commons/lang/
あとはjarファイルをクラスパスに追加するだけです。
では、動作確認してみましょう。
適当にクラスを用意
今回はプロパティのgetter/setterのみを持つ簡単なクラスを作成。
public class User { private String name; private int age; // 以下はnameとageの getter/setterが続く }
commonsのToStringBuilderを使用
public static void main(String[] args) { User user = new User(); user.setName("KAROUS"); user.setAge(18); String out = ToStringBuilder .reflectionToString(user, ToStringStyle.MULTI_LINE_STYLE) .toString(); System.out.println(out); }
実行結果
commons.lang.test.User@1b67f74[ name=KAROUS age=18 ]
地味に便利ですな。
知らないクラスを使用するときはとりあえずこいつでプロパティを表示してみると、いろいろ分かります。
Javaの設計と実装
本のタイトルは「ソースコードリーディングから学ぶJavaの設計と実装」です。
技術評論社
http://www.gihyo.co.jp/books/syoseki.php/4-7741-2950-X
プログラミングを学ぶ良い方法の一つとして、他人の書いた良質なコードを読むことが挙げられます。今日では「オープンソース」の概念が広まり、インターネットを介して簡単に良質なコードを入手することができます。
しかし、「コードの読み方」に関する情報は現時点では非常に乏しい。
まあ、はじめに何でもかんでもインターネットなどで調べようとせず、まず自分で考えてみるということは重要だと思うのですが、オープンソースはある程度の規模があり、いきなり読むのは正直つらいものがありました。
結局、気になったオープンソースのソフトウェアを入手→コードを読もうとする→よくわからなくなって挫折、のループを繰り返していたように思います。
この本は、現時点では数少ない、「コードリーディング」をテーマとして取り上げた本です。実際に日本で流行っている、Javaオープンソースソフトウェアを取り上げて説明しています。章立ては以下のとおり。
- ソースコードを読む
- 汎用ライブラリ Jakarta Commons Logging/Pool
- テスティングフレームワーク JUnit
- Webアプリケーションフレームワーク Struts
- 統合開発環境 Eclipse
- Webアプリケーションサーバ Apache Tomcat
- テンプレートエンジン Jakarta Velocity
- DIコンテナ Spring Framework
- データベースエンジン HSQLDB
- ソースコード読解の手法
基本的には、各オープンソースソフトウェアで使用されている主要なテクニックがどのように実装されているか、を実際にコードを載せて説明しています。
しかし、私が良いと思ったのは10章の「ソースコード読解の手法」です。この章には、著者のソースコード読解の手法が説明されています。先にも述べたようにコードリーディングの情報は非常に少ないため、この情報は貴重と言えるでしょう。*1
値段も2480円となっており、技術書としては安いほうだと思います。私のようにソースコードの読み方がわからず、迷子になっている人にはオススメしたい本です。
*1: 情けない話なのですが、ここを読むことで、オブジェクト指向言語/非オブジェクト指向言語で、ソースコードの読み方のアプローチが異なることを知りました。
カラス 真ボス
少しずつ攻略してきた「カラス」ですが、先日、ついに真ボスを倒しました。総合レベルは230(ショット54, ソード100(MAX), シールド76)、スコアは約24億です。真ボス時は、音楽も主人公の顔もぶっとんでます。ぜひ見てほしいですね。
というわけで、真ボス出現の条件は以下のようになっています。
- ハードモードでやること
- 5ボスを破壊?(これについては未確認)
- 各ステージに1つずつ隠されている会社ロゴ(M)アイテムを全回収
まず1ですが、実はノーマルとハードはあまり難易度が変わりません。このゲームは弾を「避ける」のではなく、吸収するのがメインだからです。ハードは多少弾が増えますが、誤差の範囲と言っても過言ではないです。
2については、シールドがレベル50以上、またソードのレベルが十分(70くらい)であれば、余裕で達成できるでしょう。ボスが弾幕をばらまく→弾幕につっこんで反射シールドを展開→反射シールドにより、弾幕に穴があくのでつっこんでソードで斬る をひたすら続けます。
次に3ですが、これが少々やっかいです。ロゴアイテムはステージ中の、背景のあるポイントを撃ちこむことで出現します。ただし、やみくもに撃ちこむのはダメで、ステージごとに出やすい武器が決まっています。(たとえば、ステージ1はソードでは出現しない)詳細は以下のとおり。
- ステージ1:ステージ終盤(町の背景)にある、細長い建物の目の前を「ショット」で攻撃
- ステージ2:ボスのWARNINGが出る直前の、左側に出現する背景の中心を「ショット」で攻撃
- ステージ3:中ボス後の町の背景の、3つ並んで立っている青い建物の目の前を「ソード」で攻撃
- ステージ4:ステージ開始後、すぐに線路が見えてくるが、その線路の一番右端(画面右端)に「シールド」で乗っかる
- ステージ5:ステージ開始後、すぐに画面左端に道路が見えてくる。その道路上のポイントに「シールド」で乗っかる
上記のうち、ステージ4,5はなかなか出ません。出現ポイントが見えてからすぐに対処するようにしないとスクロールアウトするので注意。また、どうしても出現ポイントが見つからない場合は、D.F.Sを使いましょう。D.F.S中はポイントが見えるようになります。
真ボス出現条件を満たすと、5ボス破壊時に画面が暗転→「白い」カラスの羽が舞い落ちる演出→真ボス登場となります。
ところで、カラスって英語のスペルは「KAROUS」なのね...
カラスをプレイ
近所のゲームセンターになぜか入荷してたのでプレイ。ゲームモードはイージー, ノーマル, ハードがあり、このうちノーマルを。
...でクリアしました。
感想は...よくわかりません。なにもかも。
システムとしては、自機には以下の3つの武器が用意されています。
- ショット: 遠距離攻撃
- ソード : 近距離攻撃
- シールド: 弾を相殺 or 攻撃
これらの使用は無制限であり、また使用すればするほどレベルアップし、性能が向上(範囲拡大 and 威力アップ)します。この辺は私は好みなのですが、納得いかんのがシールドの性能。レベルが低いうちは適用範囲が狭いのですが、Lvが上がると範囲拡大し、大量の弾幕でも防ぐことができます。また、シールドで防げない弾幕は、ステージ4のボス/中ボスのレーザーのみであり、それ以外の弾幕は全て防げます。
さらにこのゲームは敵本体の当たり判定がない and 自機の当たり判定が小さいです。
これらのことから、ある程度シールドのレベルを上げておき、画面一番下でじっとしているだけで生存できます。ラスボスは実は倒したというわけでなく、身を守っていたら自爆されました。わけわかりません。クリア時には、「やった!」というよりは、「こんなのでいいのかな?」って感じでした。
もう少しバランスをなんとかしてほしかった...