JavaScriptでDIできる Injector.js つくった

仕事でRobotlegsというAS3のフレームワークを使っているのだけど、DI機構がかっこよかったので、Injectorだけ真似て作ってみた。

mizchi/injector.js · GitHub https://github.com/mizchi/injector.js

Robotlegs AS3 Micro-Architecture - Home http://www.robotlegs.org/

使い方

ConstructorClass.inject に {key:InjectedClass} で実体化するインスタンス名と実体化するクラスを登録する。
CoffeeScriptで違和感無いような記法でチューニングしてある。

class UserModel

class X_View
  Injector.register(@)
  @inject:
    model: UserModel

Injector.mapSingleton UserModel, new UserModel
x = new X_View
console.log x.model


JSだとこうなる

function UserModel(){};

function X_View(){};
X_View.inject = {
  model:UserModel
}
Injector.register(X_View);

Injector.mapSingleton(UserModel, new UserModel);
var x = new X_View;
console.log(x.model);

JSだとあんまりかっこよくないですね…


子のInjectorも作れる

childInjector = new Injector()

他、明示的に呼ぶ必要があるが、Injector.ensureProperties(hoge)を使えば、hogeHogeプロトタイプに登録されたすべてのinject対象をみたしていることを確認できる(そうでない場合は例外を吐く)

詳細はREADMEにて。bower にも登録したので bower install injectorでもいい。node.jsでもnpm installすれば使えるんじゃないかな。(使いたいかはともかく)

仕組み

Injector.mapSingletonでは、Injector.register(Hoge) でHogeクラスのprototypeに登録している。
Injector.mapValueでは、インスタンスごとにgetterで返すクラスをシングルトン化して、実体を this._...に保存している。

現時点での欠点として、mapValueはそのプロパティを最初に参照した時の遅延評価になってしまう。インスタンスをInjectorに通知したり、Injectされる専用のコンストラクタ処理を持つクラスを用意したくなかったので、このような形になった。

思想

mapValueはどちらかというとオマケで、mapSingletonが主役。
DIを使ってUIを構築してきた経験上、Viewはリスト構造以外ほとんどシングルトンであり、DIがない環境だとシングルトンやグローバル変数で取得せざるを得なくなり、どこから取得されているかわからなくなる。これはコードをスパゲティ化させる要因になりうる。

そのクラス内の名前空間において、injectされるインスタンスその場所を明示的に設定すると、見通しがよくなる。
だいたいregisterするのはそのクラスの宣言の中だが、map するのはメインの処理がはじまって以降なので、動的にinjectすることはあまり想定していない。そのようなケースは既に破綻している。

酷使するとメモリが若干程度は漏れそうな気がするが、パフォーマンスセンシティブな箇所よりはアーキテクチャを破綻させないようピンポイントで使うといいと思う。サンプルにUserModelとか書いているのは、もっともシングルトン化されそうなクラスだから。

いろいろ粗いので PullRequestください。以上。