初めまして。まだプログラマー歴2年未満の若輩ですが、少しずつ自分の勉強した内容をアウトプットさせていただきます。
間違いなどありましたら是非ご指摘の方よろしくお願い致します。
とりあえず、投稿のテストも兼ねて、自分が今まで何となく使っていたJavaフレームワークの基幹システムであるDI(それを実装したDIコンテナ)について、簡単に勉強した内容を纏めさせていただきます。
#DIとは?
依存性の注入(Dependency Injection)の略。
IOC(Inversion of Control:制御の反転)の原則を実現するソフトウェアデザインパターン。
あるクラス(仮にインターフェイスクラスと呼ぶ)に必要となるクラス(仮にコンポーネントクラスと呼ぶ)を設定する事(この設定動作を「インジェクションする」ともいう)。
DIを用いることで、ロジックを構成するインスタンスの生成と依存関係をインターフェイスクラスから分離することができる。
#DIの背景
アプリケーション開発の規模が大きくなってくると、1つのロジックを複数のコンポーネントクラスを組み合わせながら実装する事が多くなる。
そうなってくると、クラスの結合度が段々と高くなっていき、部分的な実装の差し替えによる変更コストが高くなる。
// ユーザーに関する処理を行うインターフェイスクラス
public class UserServiceImpl implements UserService {
// データ永続化に関わるコンポーネントクラス
private UserRepository userRepo;
private AddressRepository addressRepo;
/* コンストラクタ */
public UserServiceImpl() {
this.userRepo = new UserRepositoryImpl();
this.addressRepo = new AddressRepositoryImpl();
}
}
このようなクラスを実装する場合、事前にコンポーネントクラス(UserRepositoryImplとAddressRepositoryImpl)が揃っている必要がある。
もしもこれらが未完成であった場合、ダミークラスを利用することになるだろうが、開発規模が大きくなるとダミークラスの差し替え一つにも
変更コストが大きくなっていく。
このようなクラスの結合度を低くする方法として、DIの考え方での実装を行ってみる。
具体的にいうと、インターフェイスクラス内部でのコンポーネントクラスのインスタンス化をやめ、コンポーネントクラスのインターフェイスを引数で受け取る事が挙げられる。
/* コンストラクタ */
public UserServiceImpl(UserRepository userRepo,AddressRepository addressRepo) {
this.userRepo = userRepo;
this.addressRepo = addressRepo;
}
UserRepository userRepo = new UserReposiotryImpl();
AddressRepository addressRepo = new AddressRepositoryImpl();
UserService userService = new UserServiceImpl(userRepo,addressRepo);
これにより、UserServiceImpl内部を変更することなく、呼び出し側でダミーとコンポーネント実装クラスの差し替えが可能となる。
ただ、上記の実装でも、結局インターフェイスクラスに対する各コンポーネントクラスの設定は手動で行うことになり、変更コストは大きいまま。
できれば、これらの設定も全て自動化してしまいたい。それを実現してくれるのがDIコンテナとなる。
#DIコンテナとは
DIを自動で行ってくれる基盤をDIコンテナという。
インターフェイスの呼び出し元クラス(呼び出し元クラスと呼ぶ)は、上記のように手作業でインターフェイスクラスの設定や実装を行うのではなく、
DIコンテナを経由して、コンポーネントクラスを設定された状態のインターフェイスクラスのインスタンスを取得することができる。
勿論、コンポーネントクラスがさらに別のコンポーネントクラスに依存していた場合、それに対する設定も行ってくれる。
#実装例
// SpringのDIコンテナ機能による実装
// 事前に、DIコンテナに関する設定を全て完了させている
public class UserServiceImpl implements UserService {
@Inject
private UserRepository userRepo;
@Inject
private AddressRepository addressRepo;
}
#DIコンテナを活用するメリット
依存性の解決:インターフェイスクラスとコンポーネントクラス間の依存性を解決できる
スコープ制御が可能:コンポーネントクラスのインスタンス生成から破棄までのタイミングをそれぞれ個別に設定できる。
あるコンポーネントクラスはシングルトンオブジェクトとして使い回し、あるコンポーネントクラスは毎回新規生成するといった制御が可能となる
AOPの提供:DIコンテナからコンポーネントクラスを取得する際に、一律で共通処理を挟み込むことができる
その他、ライフサイクル制御が可能etc...
#備考
インターフェイスクラスとコンポーネントクラスの関係は相対的な物で、あるクラスにとってはインターフェイスクラスであるクラスが別のクラスにとってはコンポーネントクラスであることは十分に考えられる。