KotlinでDIして遊ぶ
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