“trait”的依赖注入

Kev*_*ith 3 dependency-injection scala

鉴于以下情况FooService

scala> trait FooService { 
     |   def go: Int 
     | }
defined trait FooService
Run Code Online (Sandbox Code Playgroud)

还有一个MainService,它代表一个 main 方法。

scala> trait MainService extends FooService { 
     |   def f = go + 42
     | }
defined trait MainService
Run Code Online (Sandbox Code Playgroud)

FooService可以有一个假的(用于测试)和一个真实的实现(例如,点击数据库):

scala> object FakeService extends FooService { 
     |   def go = 10
     | }
defined object FakeService

scala> object RealService extends FooService {
     |   def go = 55 // in reality, let's say it hit the DB and got a value
     | }
defined object RealService
Run Code Online (Sandbox Code Playgroud)

在我看来,添加“跑步者”类/特征(即跑步sbt run将导致该类的执行)是可行的。它看起来像:

scala> class Main extends MainService {
     |   override def go = RealService.go
     | }
defined class Main
Run Code Online (Sandbox Code Playgroud)

我也可以定义一个测试:

scala> class Test extends MainService {
     |   override def go = FakeService.go
     | }
defined class Test
Run Code Online (Sandbox Code Playgroud)

我不太确定这是定义 real 与 test 的惯用方式MainService。请告诉我。

bjf*_*her 5

您可以使用流行的蛋糕模式,也称为执行依赖项注入的“Scala 方式”。

乔恩(Jon)写了一篇很棒的博客文章,其中包含演练(他还列出了一些替代方案)。

首先, 的特征FooService

trait FooServiceComponent {
  val fooService: FooService

  trait FooService {
    def go: Int
  }
}
Run Code Online (Sandbox Code Playgroud)

也就是说,我们需要两件事:1. 实际对象,2. 它的定义/实现。两者命名空间在一起。好的。以下是FakeReal版本:

trait FakeService extends FooServiceComponent {
  class FakeService extends FooService {
    def go = 10
  }
}

trait RealService extends FooServiceComponent {
  class RealService extends FooService {
    def go = 55
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,对于MainService

trait MainServiceComponent { this: FooServiceComponent =>
  val mainService: MainService

  class MainService extends FooService {
    def f = go + 42
    def go = fooService.go // using fooService
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意自键入this: FooServiceComponent,这是 Scala 的一种表达方式,MainServiceComponent依赖于FooServiceComponent. 如果您尝试实例化MainServiceComponent而不混合任何内容FooServiceComponent,那么您将收到编译时错误。好的。:)

现在,让我们创建具有不同特征的TestMain对象:

object Test extends MainServiceComponent with FakeService {
  val mainService = new MainService()
  val fooService = new FakeService()
}

object Main extends MainServiceComponent with RealService {
  val mainService = new MainService()
  val fooService = new RealService()
}
Run Code Online (Sandbox Code Playgroud)

请注意,由于命名空间的原因,FakeService无法访问它,Main因为它没有混合在一起。很好。:) 另请注意,您可以将类的任何实例化延迟到此时,这很方便,因为您可以轻松地使用注册表或模拟库将它们全部替换到一个位置。

结果:

println(Test.mainService.f) // -> 52
println(Main.mainService.f) // -> 97
Run Code Online (Sandbox Code Playgroud)

我希望这有帮助。