LoginSignup
44
40

More than 5 years have passed since last update.

[Scala]implicit parameterを使ったDI

Posted at

ScalaでDIを行う方法はいろいろありますが、最近お気に入りのDIのやりかたの紹介です。

implicit defと、implicit parameterを組み合わせて必要なコンポーネントの受け渡しをコンパイラーに任せてしまいます。

利点

  • Scalaの標準機能のみで簡単に実装できてライブラリ不要
  • 全てコードで記述できるので、XML地獄や新しいDSLを覚える必要がない
  • 使いたいコンポーネントをコンストラクタに追加するだけで使えて楽

欠点

  • コンストラクターDIしか出来ない

手順

  1. インジェクションしたいコンポーネントをimplicit defで列挙したクラス(トレイト)を作る
  2. コンポーネントを使うクラスで、使いたいコンポーネントをimplicit parameterで定義
  3. コンポーネントを使うクラスを生成する際にコンポーネントを列挙したクラスのフィールドをimportする

のが、実装の大まかな流れになります。

サンプルコード

では、早速サンプルコードを。

基本部分

Repositories.scala
trait Repositories{
  // DIしたいコンポーネントをimplicit defで列挙
  implicit def userRepo : UserRepository
  implicit def mailService : MailService
}
UserRepository.scala

// DIで渡したいコンポーネントの定義
trait UserRepository{
  def getUser(id : Long) : Option[UserDAO]
}

case class UserDAO(id : Long){
  //...implements
}
UserFacade.scala

// 使用したいコンポーネントを、コンストラクタのimplicit parameterに指定する
class UserFacade()(implicit userRepo : UserRepository){

  def changeNickname(id : Long,nickname : String) : Optino[UserDAO]= {
    userRepo.getUser(id).map(_.setAndSaveNickname(nickname))
  }
} 


Factory.scala

class Factory(repos : Repositories){

  // コンポーネントを列挙したクラスのフィールドをimportしてやる
  import repos._

  lazy val userFacade = new UserFacade() // 勝手に必要なコンポーネントが渡される
}


使い方

Production.scala
object RepositoriesForProduction extends Repositories{
  // lazy valにしておくと、不要なコンポーネントが初期化されない
  lazy val userRepo = new TrueImplementedUserRepository()
  lazy val mailService = new TrueImplmentedMailService()
}
val factory = new Factory(RepositoriesForProduction )
val userFacade = factory.userFacade
userFacade.changeNickname(1,"クドリャフカ")
Test.scala

object RepositoriesForTest extends Repositories{
  // テスト用の場合は、mock化したコンポーネント
  lazy val userRepo = mock[UserRepository]
  lazy val mailService = mock[MailService]
}
// 初期化の方法は変わらず
val factory = new Factory(RepositoriesForTest)
val userFacade = factory.userFacade
userFacade.changeNickname(1,"クドリャフカ")

といった感じになります。

補足

あと、先ほど利点で挙げた「使いたいコンポーネントをコンストラクタに書くだけでいい」という事を補足しておきます。
例えば、「パスワードリセットして通知メールを送る」機能を追加するとします。
その場合、UserFacade.scalaだけを次のように修正します。

UserFacade.scala

// メール機能を使いたいのでMailServiceを追加
class UserFacade()(implicit userRepo : UserRepository,mailService : MailService){

  def changeNickname(id : Long,nickname : String) : Optino[UserDAO]= {
    userRepo.getUser(id).map(_.setAndSaveNickname(nickname))
  }

  def resetPassword() = {
    userRepo.getUser(id).foreach( user => {
      val newPassword = user.resetPassword() // resetPassword()は実装済みとする
      emailService.sendTo(user,"パスワードリセット",s"新しいパスワードは${newPassword}です")
    })
  }
} 


これだけで初期化部分の修正なしに、UserFacadeでEmailServiceを使えるようになります。

44
40
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
40