次のようなコードを考える。
case class User(id: Int, name: String, mail: String)
class DB(host: String, port: Int) {
def findUserByMail(mail: String): Option[User] = ...
}
class App(db: DB) {
def showUserName(mail: String) {
db.findUserByMail(mail) match {
case None => println("no such user: " + mail)
case Some(user) => println("name: " + user.name)
}
}
}
このとき、どこでどのように App
や DB
のインスタンスを作るかが問題になる。
繋ぐデータベースを変えたくなったときに困る。
class App {
val db = new DB("example.com", 7777)
...
}
object Great {
val app = new App
...
}
object Good {
val app = new App
...
}
規模が大きくなると悲しいことになりそう。
object Great {
val app = new App(new DB("example.com", 7777))
...
}
object Good {
val app = new App(new DB("example.com", 7777))
...
}
object TestSuite {
val app = new App(new DB("test.example.com", 8888))
...
}
次のやり方でインスタンスを組み立てる場所を切り離してまとめることができる。
trait DBComponent {
val db: DB
class DB {
...
}
}
trait AppComponent { this: DBComponent =>
val app: App
class App {
...
}
}
trait NiceEnvironment extends DBComponent with AppComponent {
val db = new DB("nice.host", 777)
val app = new App
}
object Great extends NiceEnvironment {
...
}
object Good extends NiceEnvironment {
...
}
trait TestEnvironment extends DBComponent with AppComponent {
val db = new MockDB
val app = new App
}
object TestSuite extends TestEnvironment {
...
}
ちなみに上記の this: DBComponent =>
は自分型アノテーション (self-type annotation) という。AppComponent
を継承する場合は DBComponent
も継承していないといけない、という制約を課すのに使っている。