Javaで使えるDIコンテナのひとつであるPicoContainerのスバラシさ(の一部)について語ってみる。たぶん、他のDIコンテナにも共通しているはず。
PicoContainerをいじり始めてしばらくは、マニュアルどおりにインタフェースと実装の分離を進めていった。Singletonがなくなったり依存関係が整理できたりととてもよろしかったんだけど、インタフェースやコンストラクタ引数の増加といった、弊害もないわけじゃなかった。
しばらくして、コンストラクタ引数に配列を使うようになって状況が変わった。
それまで別々だったクラスに共通のインタフェースをつけて、ぜんぶPicoContainer#registerComponentImplementation(Class)で登録しておく。使う側のクラスでは、コンストラクタにそのインタフェースの配列を指定しておくだけで、PicoContainerが登録されている中から該当するインタフェースを実装しているオブジェクトを見つけて、渡してくれる。
結果的に、それぞれの具象クラスに依存する部分は登録部分だけになる。これで、より少ない、適切なインタフェースでシステムが構成されたことになる。より抽象度の高いレベルでの依存関係になるし、コンストラクタ引数も少なくて済む。シンプルになったわけだ。
たとえば、あるイベントリスナの複数の実装が、そのライフタイムのあいだずっと、あるオブジェクトを監視していたいとする。次の例にあるようなイベントリスナ管理コンポーネントを作っておくことで実現できる。
// import文は省略 // イベントリスナを管理するコンポーネント public class MyListenerManager implements Startable { private Target subject; private MyListener listeners; public MyListenerManager(Target subject, MyListener listeners) { this.subject = subject; this.listeners = listeners; } public void start() { for (MyListener listener : listeners { subject.addListener(listener); } } public void stop() { for (MyListener listener : listeners { subject.removeListener(listener); } } } // 利用例 public static void main(String[] args) { MutablePicoContainer pico = new DefaultPicoContainer(); pico.registerComponentImplementation(Target.class, TargetImpl.class); // MyListenerの実装クラスを複数登録しておく。 pico.registerComponentImplementation(MyListenerImpl1.class); pico.registerComponentImplementation(MyListenerImpl2.class); // MyListenerManagerも登録する。 pico.registerComponentImplementation(MyListenerManager.class); // その他必要なコンポーネントがあれば登録 pico.registerComponentImplementation(MyComponent.class, MyComponentImpl.class); .... // PicoContainer#start()からMyListenerManager#start()が呼ばれて // イベントリスナが登録される。 pico.start(); // 何かやる... // イベントリスナが登録解除される。 pico.stop(); }
こんな風にしておけば、特にイベントリスナの面倒を見なくても、ライフサイクル管理の一環として登録やら削除やらをしてくれる。MyListenerManagerは、どんな実装クラスがあるのか気にする必要はない。Startableを実装しておくのと、コンストラクタにリスナインタフェースの配列を指定しておくのがポイント。
DIコンテナを使わないと、基本的に、誰かが明示的にMyListenerImpl1やMyListenerImpl2をインスタンス化して、どっかのタイミングでリスナ登録して、どっかのタイミングでリスナ削除しなくちゃいけない。さらに、MyListenerImpl1をインスタンス化するクラスは、MyListenerImpl1のコンストラクタに渡す引数があればそれを知らないけない。MyListenerImpl2についても同様。
DIコンテナのおかげで、そういう面倒なことを考えなくても良くなる。各オブジェクトを自分の仕事に専念させられるし、結合度も最低限に保てる。スバラシイ。
ちなみにPicoContainerでは、引数を配列にした場合、登録した順序でインスタンスが入っている。だから、順序が問題になる場合(たとえばメニュー定義とか)にも使える。
オブジェクトの生成とライフサイクル管理ってのは、多くのアプリケーションに共通した大問題なんだよね。そこを取り扱ってくれるDIコンテナは、想像以上にたくさんの問題を解決してくれる。だから、いつも「ありがとうぅっ!! PicoContainerァァッ!!!」と思いながらコードを書いているのだ。