Springをやっており、依存性の注入(DI)という謎の言葉が出てきたので、自分の理解を確認する意味でそれについて書いておく。
(ただ、正直、こちらに全て記されている感はある。)
(記事内のコードはNTTデータの『Spring 徹底入門』からお借りした。)
クラスAを動かすためにはクラスBが必要であること
「クラスAを動かすためにクラスBが必要」ということをクラスAの中に書かずに、外に書いて外から注入できるようにすること
基本的には単体テストをしやすくするため。
依存を中で書いてしまうと、テスト段階において、クラスBの事情でクラスAのコードをわざわざ書き換えなくてはならないケースが出てくる。(上記のブログではTwitterの例が挙げられている)
そこで、クラスAの中のコードを書き換えなくても単体テストができるように、依存性を外から注入するということをする。
具体的には、クラスBのインスタンスをクラスAの外で生成した上で、クラスAに渡す。
public interface PasswordEncoder {
String encode(String rawPassword);
}
public interface UserRepository {
User save(User user);
int countByUsername(String username);
}
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
//クラスのコンストラクタ内でインスタンスを作成
public userServiceImpl(javax.sql.DataSource datasource){
this.userRepository = new JdbcUserRepository(datasource);
this.passwordEncoder = new BCryptPasswordEncoder();
}
//以下、省略
}
クラスBのインスタンスをクラスAの中で生成してしまっている。
仮にJdbcUserRepository
クラスやBCryptPasswordEncoder
クラスがまだ実装されていない場合は、UserServiceImpl
クラス内のそれらを別のダミークラスに書き換えてテストしなければならない。
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
//クラスの外で生成したインスタンスをコンストラクタに渡す
public userServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder){
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
//以下、省略
}
//外でインスタンス生成
UserRepository userRepository = new JdbcUserRepository();
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
UserService userService = new UserServiceImpl(userRepository, passwordEncoder);
クラスBのインスタンスをクラスAの外で生成し、コンストラクタに渡している。
JdbcUserRepository
やBCryptPasswordEncoder
がまだ実装されていない場合、クラス内のコードを書き換える必要はなく、下記のように外でダミークラスのインスタンスを作成し、それを渡してやればよい。
//ダミーに差し替え
UserRepository userRepository = new DummyUserRepository();
PasswordEncoder passwordEncoder = new DummyPasswordEncoder();
UserService userService = new UserServiceImpl(userRepository, passwordEncoder);
それでも、まだ手動でインスタンス生成の部分のコードを差し替えなければならないのは面倒くさい。
そこでDIコンテナが登場する。 今まで手書きする必要があったインスタンス生成の部分をDIコンテナに任せることができる。
例えば(アノーテーションベース)、
@Component
public class UserRepositoryImpl implements UserRepository {
// ...
}
@Component
public class BCryptPasswordEncoder implements PasswordEncoder {
// ...
}
@ComponentアノーテーションでDIコンテナにクラスを登録し、
@Component
public class UserServiceImpl implements UserService {
@Autowired
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
// ...
}
}
@AutowiredでDIコンテナがインスタンスを自動生成してくれ、注入までやってくれる。インスタンス生成のコードは書かなくてよい。
DIコンテナの設定で、状況に応じてどのクラスのどのインスタンスを注入するかを変更できると思うが、まだ調べていない。 それも含めて、DIコンテナのメリットはインスタンス生成の手間が省けるというだけではなく、色々あるのだと思う。 その詳細についてはまた次回書こうかと思う。
依存される側の事情でそれに依存している側のコードを書き換えるのは面倒くさいから、そうならないようにインスタンスを外で生成し注入するっていうやり方なのだろう。依存性の注入っていうのは。