ホームへ

依存性の注入(DI)とは

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の外で生成し、コンストラクタに渡している。

JdbcUserRepositoryBCryptPasswordEncoderがまだ実装されていない場合、クラス内のコードを書き換える必要はなく、下記のように外でダミークラスのインスタンスを作成し、それを渡してやればよい。

//ダミーに差し替え
UserRepository userRepository = new DummyUserRepository(); 
PasswordEncoder passwordEncoder = new DummyPasswordEncoder();
UserService userService = new UserServiceImpl(userRepository, passwordEncoder);

それでも、まだ手動でインスタンス生成の部分のコードを差し替えなければならないのは面倒くさい。

そこでDIコンテナ!

そこで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コンテナのメリットはインスタンス生成の手間が省けるというだけではなく、色々あるのだと思う。 その詳細についてはまた次回書こうかと思う。

終わりに

依存される側の事情でそれに依存している側のコードを書き換えるのは面倒くさいから、そうならないようにインスタンスを外で生成し注入するっていうやり方なのだろう。依存性の注入っていうのは。

2018/07/25 16:27:05に更新

カプセルで書く技術ログ
Capsule-Man on Github