あまり目新しい内容ではないですが、Google Guice理解のために、改めてまとめました。
ScalaにおけるDI
Javaなどの言語と同様に、Scalaにおいても依存性注入(DI)を実現する方法はいくつかあるようです。
- Constructor InjectionやSetter Injectionを使う
- Cake PatternなどのScala特有の言語機能を使った方法
- (Google Guiceなどの)DIフレームワークを使う
- 構造的部分型を使うetc....
この記事では3.のGoogle Guiceを使う方法について調べました。そういえばPlay Frameworkの2.4でも導入されるとか聞いたような……?
Google Guiceの基本的な使い方(Java)
いくつかの典型的なパターンと比較しているという点でも、公式のチュートリアルがとても分かりやすいかなと思います。GuiceはもともとJava用なので、Javaで書いてある……。
大事な部分だけ抜き出すとこんな感じ。
interface Service {
void doSomething();
}
class RealService implements Service {
@Override
public void doSomething() {
// .....
}
}
class FakeService implements Service {
@Override
public void doSomething() {
// .....
}
}
やりたいことは、RealService
とFakeService
どちらを使うかを状況によって切り替えることです。例えばテスト時にだけFakeService
を使うようにしたい。そして、どの具体的な実装を使うかという知識は、Service
とは離れた場所に持たせたい。Guiceでは次のようなお作法に従ってDIを実現するようです。
- 依存性を注入されるクラスでは、依存関係にあるクラスをコンストラクタで受け取る
- コンストラクタには
@Inject
アノテーションを付けておく
- コンストラクタには
- どう配線するか(どのインタフェースにどの具体的な実装を入れるか)はModuleにまとめる
-
Guice.createInjector
を用いることで、ModuleからInjectorを生成する -
Injector
のgetInstance
メソッドを呼ぶことで、Moduleで記述された参照関係に基づいて実際のインスタンスが得られる
このルールに基づいて、Service
を利用するクラスであるServiceUser
を記述してみます。
class ServiceUser {
private final Service service;
@Inject
public ServiceUser(Service service) {
this.service = service;
}
// serviceを利用したコードは省略...
}
具体的にどのService
を利用するかは、ServiceUser
に現れていません。次はようやく、ServiceUser
を使う例です。
public class ServiceModule extends AbstractModule {
@Override
protected void configure() {
bind(Service.class).to(RealService.class);
}
}
public static void main(String[] args) {
Injector injector = Guice.createInjector(new ServiceModule());
ServiceUser serviceUser = injector.getInstance(ServiceUser.class);
// serviceUserを使ったコードは省略.....
}
Moduleを切り替えることで、依存する実装も切り替えることができるようになりました。
ScalaでGuiceはどうなるのか
依存関係にGoogle Guice 4.0を追加します。
libraryDependencies += "com.google.inject" % "guice" % "4.0"
Scalaでの使い方としては、Javaとほとんど同じように書けます。@Inject
の位置が、クラス名の後ろ、コンストラクタ引数の前になるという点に注意。
object TestDI {
import javax.inject._
import com.google.inject.AbstractModule
trait Service {
def doSomething(): Unit
}
class RealService extends Service {
def doSomething() { println("This is RealService") }
}
class FakeService extends Service {
def doSomething() { println("This is FakeService") }
}
class ServiceUser @Inject() (service: Service) {
def use() { service.doSomething() }
}
class ServiceModule extends AbstractModule {
override protected def configure() {
bind(classOf[Service]).to(classOf[RealService])
}
}
}
使う時もJavaとほぼ同様です。
import com.google.inject._
val injector: Injector = Guice.createInjector(new ServiceModule)
val user: ServiceUser = injector.getInstance(classOf[ServiceUser])
user.use //=> "This is RealService"
まとめ
Scalaでも、おおむねJavaと同じようにGuiceは使えることが分かりました……が、JavaのものをそのままScalaに移しても、あんまりScalaっぽくならないので微妙感が漂う気がします。なんかもうちょっとラップしたライブラリが欲しくなるなる……。
Playなどのフレームワークと統合した場合にGuiceはどういった使い勝手になるのかについては、今後調査したいですね……。