部品Aが部品Bを利用する場合を考えてみよう。部品Aのソースコードには通常,部品Bを利用するための記述が入り込む。これは「部品の間に釘がささった状態」(千葉氏)に例えることができる(図1[拡大表示])。部品Bだけをはずして別の場面で使うのは容易だが,部品Aには釘の頭の部分が残っている。これを無理に抜こうとすると,部品の一部が崩れてしまう。つまり「部品Aを単独で再利用しようとすれば,ソースコードを修正するしかない」(千葉氏)のである。この問題は,部品を再利用する際の足かせとなる。保守性の低下にもつながる。
こうした問題を解決するものとして,現在注目されている技術が二つある。一つはDI(Dependency Injection)*1。「依存性の注入」と訳される技術だ。もう一つはアスペクト指向。オブジェクト以外のシステム分割の軸を採り入れて,部品間の依存性をなくす。
DI(Dependency Injection) 依存性を外部で管理 |
DIは,J2EE(Java2 Platform,Enterprise Edition)の開発者の間で広まった考え方だ。「Spring Framework」や「PicoContainer」が,DIの考え方をいち早く採り入れたものとして知られている。日本では,国産の「Seasar2」も人気を集めている。
DIは,部品間の依存性を部品以外の場所で管理することにより,部品そのものから依存性を排除しようとする考え方である。依存性は,コンポーネントを管理するミドルウェアである「DIコンテナ」が処理を行う。DIコンテナが各部品の実体を生成し,それぞれの部品の依存関係を実行時に解決する。
ある部品のクラス(classA)が,他の部品のクラス(classB)の機能を呼び出したいとする(図2[拡大表示])。その機能はインタフェース(interfaceX)で定義されている。つまりインタフェースは部品の「外部仕様」で,classBはその実装である。本来なら,インタフェースさえ分かっていれば,その実装を関知しなくていいというのが部品としてあるべき姿。しかし実際にはオブジェクトを生成するときに,実装であるclassBの情報が必要となる(図2-a)。classBのクラス名を指定してオブジェクトを生成しなければならないので,結果としてclassAはclassBに依存してしまう。
DIでは,classAとclassBの仲介をDIコンテナに任せる。依存性の情報は設定ファイルに記述しておき,DIコンテナに読み込ませる(図2-b)。DIコンテナはこの情報に基づいてclassBのオブジェクトを生成し,classAのオブジェクトに渡す。
この結果,classAにはclassBを直接指定するコードが混入しない。DIコンテナから受け取ったオブジェクトに対して,インタフェース名でアクセスすればよい。classAとclassBの間には依存性がなくなる。
生成処理をコンテナが肩代わり
Seasar2を使って,DIの動作の実際を見てみよう。一連の処理を示したのが次ページの図3[拡大表示]である。アプリケーションの開発者はまず,Seasar2の設定ファイルである「.dicon」ファイルをSeasar2に読み込ませ,コンテナを生成する。 .diconファイルに記述されているのは,コンテナで動作させるクラス(コンポーネント)の情報である。ここでは「message.MessageImpl」と「name. NameImpl」という二つのクラスのコンポーネントを登録している。またオブジェクトの生成時に渡す引数の指定も行っている。
アプリケーションがコンテナに対してオブジェクトを要求すると,コンテナが条件に合うオブジェクトを自動的に生成する。ここでは,Messageインタフェースを持つオブジェクトを指定している。
登録されたコンポーネントのうち,Messageインタフェースを実装しているのはMessageImplなので,コンテナはそのオブジェクトを生成する。.diconファイルで引数を指定しているので,引数付きのコンストラクタを呼び出す。
MessageImplでは,Nameインタフェースのオブジェクトを使用する。 .diconファイルにはNameを実装したNameImplが登録されているので,コンテナはそのオブジェクトを生成し,MessageImplクラスのオブジェクトに渡す。
ここで注目すべきは,オブジェクトを生成する「new」というキーワードがソースコード中のどこにもないことである。この結果,どのクラスにも,他のクラスの実装に関する情報が混入しない。すべて外部仕様であるインタフェースだけで完結している。