Kotlin Advent Calendar 2013 10日目のエントリです。
依存性注入したい
タイトルの通り今回はKotlinでDIに挑戦。DIコンテナ使うんでなく素のKotlinでやってみる。難しいことをやろうとしているのではなく、責任外の処理を別のオブジェクトに委譲してユニットテストをやり易くしたいだけ。
まずは準備。
data class Employee(
val firstName: String,
val lastName: String
)
サンプルでありがちなEmployeeクラス。dataアノテーションについてはこちらのエントリを参照。
trait EmployeeRepository {
fun save(employee: Employee): Employee
}
で、次にDAO的なトレイトを定義。
ここでcreateEmployeeメソッドを持ったEmployeeServiceクラスを作りたい。このクラスはEmployeeRepository#saveを使う。JavaだったらEmployeeServiceのコンストラクタでEmployeeRepositoryのインスタンスを受け取ってフィールドに保持する感じになると思う。Kotlinはちょっと違う。
class EmployeeService(employeeRepos: EmployeeRepository) : EmployeeRepository by employeeRepos {
fun createEmployee(firstName: String, lastName: String): Employee {
return save(Employee(firstName, lastName))
}
}
1行目の: EmployeeRepository by employeeReposに注目。これによりクラスはEmployeeRepositoryのサブタイプになり、そのメソッドへのアクセスはコンストラクタで受け取ったEmployeeRepositoryインスタンスへ自動的に委譲される。微妙な違いだけど、emplyoeeRepos.saveという記述ではなく単にsaveとしてメソッドを呼び出しており、余計なプロパティを持つ必要がなくなる。
実際に動かしてみる。EmployeeRepositoryの実装として次のクラス(というかオブジェクト)を定義する。
object DummyEmployeeRepository: EmployeeRepository {
override fun save(employee: Employee): Employee {
println("saved: " + employee)
return employee
}
}
EmployeeServiceへ依存性を注入して実際に使う。
fun main(args: Array<String>) {
val employeeService = EmployeeService(DummyEmployeeRepository)
employeeService.createEmployee("Taro", "Nagasawa")
}
期待通りsaved: Employee(firstName=Taro, lastName=Nagasawa)と出力されるはず。
最後に、せっかくDIできるようになったんで簡単なテストを書く。
import org.junit.Test as test
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.times
class EmployeeServiceTest {
test fun 指定された名前の従業員を新たに生成し保存すること() {
val employeeRepos = mock(javaClass<EmployeeRepository>())!!
EmployeeService(employeeRepos).createEmployee("Taro", "Nagasawa")
verify(employeeRepos, times(1))!!.save(Employee("Taro", "Nagasawa"))
}
}
おまけ
@ngsw_taro 太郎、それDIやない。ただのストラテジーパターンや!
— 谷本 心.do (@cero_t) 2013, 12月 10
@ngsw_taro インスタンスの生成(ライフサイクル管理を含む)の責務を持つ「DIコンテナ」がいて、ストラテジーパターンなどを実現してるのが、DI。
— 谷本 心.do (@cero_t) 2013, 12月 10
すみません、釣りなタイトルになってしまった。。
と思ったらこんなツイートもいただきました。
@ngsw_taro DI と言っていいと思います。DI の主眼は外から実装を渡すことで IoC を実現すること。Strategy は複数の実装を切り替えることで、必ずしも外から実装を渡す必要はないはず。
— shuhei (@7to3) 2013, 12月 10