SCALDIでDependency Injection

kotohが2015/09/30 17:31:24に投稿

scaldi で dependency injection

はじめに

dependency injection とは 日本語では依存性の注入と表現し、よくDIを表記されます。
オブジェクトが依存するオブジェクトと強く結びつけられていると、変更容易性、テスタビリティの低下につながります。
DIはオブジェクトに対して、依存するオブジェクトを外部から注入することでオブジェクトの結合を疎に保つためのパターンです。

DIを提供するフレームワークやライブラリも存在します。
今回は、scaldiというライブラリを簡単に紹介します。

scaldi

scaldi は scala にDIを提供するライブラリです。
scaldiは他のライブラリに依存しないので、軽量です。
次の3つのコンポーネントが核になります。

  • Injector
  • Module
  • Injectable

Injector

型とインスタンスの関連付けを行うコンポーネントです。

Module

Injector, Injectableを継承しています。
InjectorのDSL Wrapper です、 Moduleが提供するDSLによってクラスとインスタンスの関連付けがより直感的な記述ができるようになります。
次のように書きます。

  bind [Transport] to new MessageTransport
  bind [Service] to  new MessageService(inject[Transport])

binding to を使って次のように書くこともできます。これは上と同じです。

  binding to new MessageTransport
  binding to  new MessageService(inject[Transport])

Injectable

injectメソッドを提供します。inject[型]で型に対してバインドされたインスタンスを取得することができます。
injectを使う場合は、Injector型のimplicit引数が必要になります。
次のように書きます。

val transport = inject[Transport]

サンプルコード

scaldiを使う場合と使わない場合のサンプルコードを見てみましょう。

scaldiを使わない場合

次のサンプルではコンストラクタを使って、MessageServiceにMessageTransportを注入しています。

object Main extends App {
  new SimpleExample().run
}

class SimpleExample {
  def run: Unit = {
    val transport = new MessageTransport
    val output: Service = new MessageService(transport)
    println(output.execute("Scaldi"))
  }
}

trait Transport {
  def send(s: String)
}

trait Service {
  def execute(x: String): String
}

class MessageService(transport: Transport) extends Service {
  override def execute(x: String): String = {
    val ret = "Hello " + x
    transport.send(ret)
    ret
  }
}

class MessageTransport extends Transport {
  override def send(s: String) = println("Sending message: " + s)
}

scaldiを使う場合

build.sbtに

libraryDependencies += "org.scaldi" %% "scaldi" % "0.5.6"

を追加して下さい。

import scaldi.{Injector, Injectable, Module}

object Main extends App {
  new ScaldiExapmle()(new UserModule).run // 1
}

class ScaldiExapmle(implicit inj: Injector) extends Injectable {
  def run: Unit = {
    val output: Service = inject[Service]  // 2
    println(output.execute("Scaldi"))
  }
}

class UserModule extends Module { // 3
  bind [Transport] to new MessageTransport
  bind [Service] to  new MessageService(inject[Transport])
}

trait Transport {
  def send(s: String)
}

trait Service {
  def execute(x: String): String
}

class MessageService(transport:Transport) extends Service {
  override def execute(x: String): String = {
    val ret="Hello " + x
    transport.send(ret)
    ret
  }
}

class MessageTransport extends Transport {
  override def send(s: String) = println("Sending message: " + s)
}
  1. injectする為に必要なInjector(Module)をimplicit引数で渡しています。implicit引数は、カリー化された関数の最後の引数でしか渡せないので、このような書き方になります。
  2. Injectableのinjectメソットで実際にバインドされたインスタンスを取得することができます。
  3. Moduleを継承したUserModuleで型に対するインスタンスを定義しています。

おわりに

scaldiの基本的な使い方を説明しました。
依存関係をMuduleでまとめて管理できるので、見通しがよくなりました。今回は触れませんでしたが、分割したModuleの合成も可能です。
playやakkaに対応したライブラリも提供されています。
ScalaではDIといえばcake patternが有名ですが、scaldiも使えそうです。