Codin’ In The Free World

2009-11-19

[][] ActionScriptプログラマのためのDIコンテナ - SmartyPants-IoC

DI - 依存性の注入

DIについては以下のようなエントリを見ておくといいです。

ファウラーの記事の訳

http://kakutani.com/trans/fowler/injection.html


perlを使った分かりやすいDIの解説

http://dann.g.hatena.ne.jp/dann/20080911/p1

http://dann.g.hatena.ne.jp/dann/20080913/p1


ASでのDIコンテナ

ActionScriptでDIを行いたいときに次のようなDIコンテナが存在するようです。

参考: FlexのDIコンテナ比較 - よねのはてな

http://d.hatena.ne.jp/yone098/20090704/1246846080

  • Prana Framework
  • SmartyPants-IoC
  • swizframework

ということで、Google Guiceを参考にしているというSmartyPants-IoCを使ってみることにしました。

SmartyPants-IoC

入手はこちらから

http://smartypants.expantra.net/

簡単な例

非常に簡単な例で、流れを追ってみます。

シンプルすぎるので、DIコンテナを使う利点がない例になっていますが

まずは流れを把握します。


クラスの準備

データを注入されるクラスを作ります。

注入ポイントをタグで指定します。[Inject]に注目してください。

public class Foo {

  [Inject]
  public var param:String;

  public function Foo() {

  }
}
Injectorの準備
import net.expantra.smartypants.SmartyPants;

var foo:Foo = new Foo();
var injector:Injector = SmartyPants.getOrCreateInjectorFor(foo);
ルールの追加

injectorからメソッドチェーンでルールを追加していきます。

下の例では、「 String型が要求されている注入ポイントを発見したら、"hogehoge"というインスタンスを注入する」というルールを追加しました。

injector.newRule().whenAskedFor(String).useValue("hogehoge");
注入

ルールの追加が完了したら、オブジェクトに注入を行います。

injector.injectInto(foo);

確認

注入ポイントであるparamフィールドに、"hogehoge"が代入されているはずです。

trace(foo.param);

同じ型を要求する複数の注入ポイント

下の例では、[Inject]タグで注入ポイントとして指定された

String型のフィールドが二つあります。

public class Foo {
  [Inject] public var param1:String;
  [Inject] public var param2:String;
  public function Foo() { }
}

先ほどの例と同じように設定を行い、注入を実行します。

import net.expantra.smartypants.SmartyPants;

var foo:Foo = new Foo();
var injector:Injector = SmartyPants.getOrCreateInjectorFor(foo);
injector.newRule().whenAskedFor(String).useValue("hogehoge");
injector.injectInto(foo);
trace(foo.param1);
trace(foo.param2);

param1にもparam2にも"hogehoge"が代入されていることが確認されました。


名前付きの注入ポイント

注入ポイントには名前をつけることが出来ます。

public class Foo {
  [Inject(name="p1")] public var param1:String;
  [Inject(name="p1")] public var param2:String;
  public function Foo() { }
}

先ほどと同じ設定で注入を実行します。

import net.expantra.smartypants.SmartyPants;

var foo:Foo = new Foo();
var injector:Injector = SmartyPants.getOrCreateInjectorFor(foo);
injector.newRule().whenAskedFor(String).useValue("hogehoge");
injector.injectInto(foo);
trace(foo.param1);
trace(foo.param2);

これはinjectIntoが例外を飛ばします。

なぜなら注入のためのルールが足りないからです。

次のように変更します。

import net.expantra.smartypants.SmartyPants;

var foo:Foo = new Foo();
var injector:Injector = SmartyPants.getOrCreateInjectorFor(foo);
injector.newRule().whenAskedFor(String).named("p1").useValue("bar");
injector.newRule().whenAskedFor(String).named("p2").useValue("buz");
injector.injectInto(foo);
trace(foo.param1);
trace(foo.param2);

これで、param1には"bar"が、param2には"buz"が注入されるようになります。

変更したルールは次のようになります。

「String型を要求する"p1"という名前の注入ポイントがあれば, "bar"というString型のインスタンスを注入する」

「String型を要求する"p2"という名前の注入ポイントがあれば, "buz"というString型のインスタンスを注入する」

こんな感じに変更しました。


前回の例でなぜ例外が投げられたかというと、

「String型を要求する"p1"という名前の注入ポイント」のためのルール、

「String型を要求する"p2"という名前の注入ポイント」のためのルールが足りなかったわけです。


ここまでのまとめ

  • [Inject]タグを使って注入ポイントを指定できる
  • [Inject(named="hoge")]というように、注入ポイントに名前をつけることが出来る
  • 注入ポイントで大事なのは、「要求する型」と「名前」
  • ルールを作るときに、whenAskedFor(要求する型).named(名前)という形でターゲットを指定する
  • named(名前)が省略されたときは、whenAskedForで指定された型を要求する注入ポイントで、名前がつけられていない全ての箇所がターゲットになる

注入ポイント

注入できるポイントは限られています。

フィールドインジェクション

publicなインスタンス変数に。

privateとかは駄目。

[Inject]
public var foo:String;
セッターインジェクション
[Inject]
public function set foo(str:String):void {
    _foo = foo;
}

Google Guiceなどでは、コンストラクタインジェクションとか出来るんですが、

そもそもSmartyPantsではオブジェクトに対する注入になっているので、そういう機能は無いようです。

注入するモノ

newRule()

で新しくルールが作成されます。

そして

whenAskedFor(TargetClass).named(name)

で条件を指定します。

次に、「この条件にあった注入ポイントに、何を注入するか」を決めなくてはなりません。

先ほどのサンプルをもう一度見てみます。

injector.newRule().whenAskedFor(String).useValue("hogehoge");

useValueで、Stringクラスのインスタンスである"hogehoge"を指定しています。

useValue以外にもいろいろな指定の仕方が出来ます。

injector.newRule().whenAskedFor(ISomeInterface).useClass( SomeClassImplementsISomeInterface );
injector.newRule().whenAskedFor().createInstanceOf();
injector.newRule().whenAskedFor().useProvider();
injector.newRule().whenAskedFor().useRuleFor();
injector.newRule().whenAskedFor().useBindableProperty();
injector.newRule().whenAskedFor().useSingleton();
injector.newRule().whenAskedFor().useSingletonOf();

さて、ここからが重要です。


といいつつ書くの疲れたので終了。