JavaScriptで動くDIコンテナを作ってみた。
JavaScriptで動くDIコンテナを作ってみました。(→計画)
ダウンロード
container.js(コンテナ本体) - ver 0.1.0
テストケースの実行結果はこちら。
-
- Yahoo Test Utilityを使っています。
- 動作はIE6とFireFox2で確認しています。
使い方
基本的な使い方
- コンテナを作成します。
- container.Container#get(name)で、コンテナからコンポーネントを取得します。
var stdout = document.getElementById( "stdout" ); // クラス function Kitten() {} Kitten.prototype = { getName: function () { return this.name; } }; // コンテナを作成。 // 引数でコンポーネントを定義する関数を渡す。 var c = new container.Container( function( binder ) { // Kittenクラスを"mii"に関連付け。 // また、コンポーネントの"name"プロパティに"mii"をインジェクション binder.bind( "mii" ).to( Kitten ).inject( { "name": "mii" } ); }); // コンテナからコンポーネントを取得する。 var mii = c.get( "mii" ); stdout.innerHTML += mii.getName();
コンポーネントの登録
「クラス指定」、「オブジェクト指定」、「プロバイダ指定」の3つがあります。
- クラス指定
- オブジェクト指定
- コンポーネントを既存のオブジェクトで指定します。
- プロバイダ指定
var c = new container.Container( function( binder ) { // クラス指定 binder.bind( "mii" ).to( Kitten ).inject( { "name": "mii" } ); // オブジェクト指定 binder.bind( "tora" ).toInstance( new Kitten( "tora" ) ); // プロバイダ指定 binder.bind( "shiro" ).toProvider( function( container ){ return new Kitten( "shiro" ); } ); });
コンポーネントのスコープ
「シングルトン」と「プロトタイプ」をサポートします。デフォルトは「シングルトン」です。
- シングルトン
- プロトタイプ
var c = new container.Container( function( binder ) { // シングルトン binder.bind( "mii" ).to( Kitten ).inject( { "name": "mii" }).scope( container.Scope.Singleton ); // プロトタイプ binder.bind( "tora" ).to( Kitten ).inject( { "name": "tora" }).scope( container.Scope.Prototype ); });
プロパティインジェクション
プロパティインジェクション(フィールドに値を直接設定)のみサポートされます。
- ハッシュで指定された値がそのままインジェクションされます。
- コンテナに登録されたコンポーネントをインジェクションするには、container.component(name) または container.components(name) を使用します。
- container.component(name)の場合、container.Container#get(name)した値がインジェクションされます。
- container.components(name)の場合、container.Container#gets(name)した値がインジェクションされます。
var c = new container.Container( function( binder ) { // プロパティインジェクションの設定 binder.bind( "mii" ).to( Kitten ).inject( { // ハッシュの値がそのままインジェクションされる。 "name": "mii", "getAge": function() { return this.age; } , // 定義済みコンポーネントのインジェクション指定 "kitten" : container.component("kitten"), "kittens" : container.components("kittens") }); binder.bind( "kitten" ).to( Kitten ).inject( { "name":"tora"}); binder.bind( "kittens" ).to( Kitten ).inject( { "name":"kuro"}); binder.bind( "kittens" ).to( Kitten ).inject( { "name":"shiro"}); });
初期化関数・破棄関数の実行
コンポーネントを作成時に呼ばれる関数(初期化関数)と破棄時に呼ばれる関数(破棄関数)を登録できます。
- 初期化関数は、コンポーネントが作成され、プロパティインジェクションされた後、インターセプタの設定前に実行されます。
- 破棄関数はcontainer.Container#destroy()を実行した際に以下の条件を満たす場合、実行されます。
- 初期化関数・破棄関数には、引数としてコンポーネントとコンテナが渡されます。
var c = new container.Container( function( binder ) { // 初期化関数・破棄関数の設定 binder.bind( "mii" ).to( Kitten ).inject( { "name": "mii" }).initialize( function( obj, container ) { // コンポーネントを初期化する処理 }).destroy( function( obj, container ) { // コンポーネントを破棄する処理 }); });
インターセプタの適用
- インターセプタは関数で指定します。
- インターセプタを適用する関数はcontainer.Matcherで指定します。
- インタセプタは複数設定できます。
var c = new container.Container( function( binder ) { var builder = binder.bind( "mii" ).to( Kitten ).inject( { "name": "mii" }); // インターセプタの適用 builder.intercept( function( methodInvocation ) { methodInvocation.getMethodName(); // 実行されたメソッド名を取得 methodInvocation.getThis(); // コンポーネントを取得 methodInvocation.getArguments()[0] = "foo"; // 引数を取得&変更 // 元のメソッドを実行 return methodInvocation.proceed(); }, // インターセプタを適用する関数をcontainer.Matcherで指定する。 new container.Matcher( /get.*/, /set.*/ ) ); // インターセプタは複数回設定できる。 builder.intercept( function( methodInvocation ) { return methodInvocation.proceed(); }, // すべてにマッチするcontainer.Matcherを作成するユーティリティ container.any()); });
アノテーション
コンポーネントの設定はアノテーション(もどき)で行うことも可能です。
// クラス function Kitten() {} Kitten.prototype = { getName: function () { return this.name; } }; Kitten.prototype.meta ={ // アノテーション(もどき)でのコンポーネント設定 "@Container": { // インジェクション "@Inject":{ "name" : "foo" }, //初期化関数・破棄関数 "@Initialize": function( obj, container ) {}, "@Destroy": function( obj, container ) {}, // スコープ "@Scope": container.Scope.Singleton, // インターセプタ "@Intercept": [ [ function( mi ){ return mi.proceed(); }, container.any() ], [ function( mi ){ return mi.proceed(); }, new container.Matcher( /get.*/ ) ] ] } }
getとgets
コンテナには同じ名前のコンポーネントを複数登録することができます。
// コンテナを作成。 var c = new container.Container( function( binder ) { // 同じ名前で複数のコンポーネントを登録 binder.bind( "kittens" ).to( Kitten ).inject( { "name":"mii"}); binder.bind( "kittens" ).to( Kitten ).inject( { "name":"kuro"}); binder.bind( "kittens" ).to( Kitten ).inject( { "name":"shiro"}); }); // gets()でコンテナからすべてのコンポーネントを取得する。 var kittens = c.gets( "kittens" ); stdout.innerHTML += kittens[0].getName() + "<br/>"; // "mii" stdout.innerHTML += kittens[1].getName() + "<br/>"; // "kuro" stdout.innerHTML += kittens[2].getName() + "<br/>"; // "shiro" // get()の場合、最初に登録したコンポーネントが返される。 var kitten = c.get( "kittens" ); stdout.innerHTML += kitten.getName(); // "mii"