为什么使用scala的蛋糕模式而不是抽象字段?

Noe*_*edy 25 dependency-injection scala cake-pattern

我一直在阅读通过蛋糕模式在scala中执行依赖注入.我想我明白了,但我一定错过了一些东西,因为我仍然看不到它的重点!为什么通过自我类型而不仅仅是抽象字段来声明依赖性更好?

鉴于编程中 的示例,ScalaTwitterClientComponent使用cake模式声明了这样的依赖关系:

//other trait declarations elided for clarity
...

trait TwitterClientComponent {

  self: TwitterClientUIComponent with
        TwitterLocalCacheComponent with
        TwitterServiceComponent =>

  val client: TwitterClient

  class TwitterClient(val user: TwitterUserProfile) extends Tweeter {
    def tweet(msg: String) = {
      val twt = new Tweet(user, msg, new Date)
      if (service.sendTweet(twt)) {
        localCache.saveTweet(twt)
        ui.showTweet(twt)
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这比将依赖关系声明为抽象字段更好吗?

trait TwitterClient(val user: TwitterUserProfile) extends Tweeter {
  //abstract fields instead of cake pattern self types
  val service: TwitterService
  val localCache: TwitterLocalCache
  val ui: TwitterClientUI

  def tweet(msg: String) = {
    val twt = new Tweet(user, msg, new Date)
    if (service.sendTweet(twt)) {
      localCache.saveTweet(twt)
      ui.showTweet(twt)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

看看实例化时间,也就是DI实际发生的时间(据我所知),我很难看到蛋糕的优点,特别是考虑到你需要为蛋糕声明做的额外键盘输入(封闭特性)

    //Please note, I have stripped out some implementation details from the 
    //referenced example to clarify the injection of implemented dependencies

    //Cake dependencies injected:
    trait TextClient
        extends TwitterClientComponent
        with TwitterClientUIComponent
        with TwitterLocalCacheComponent
        with TwitterServiceComponent {


      // Dependency from TwitterClientComponent:
      val client = new TwitterClient

      // Dependency from TwitterClientUIComponent:
      val ui = new TwitterClientUI

      // Dependency from TwitterLocalCacheComponent:
      val localCache = new TwitterLocalCache 

      // Dependency from TwitterServiceComponent
      val service = new TwitterService
    }
Run Code Online (Sandbox Code Playgroud)

现在再次使用抽象字段,或多或少相同!:

trait TextClient {
          //first of all no need to mixin the components

          // Dependency on TwitterClient:
          val client = new TwitterClient

          // Dependency on TwitterClientUI:
          val ui = new TwitterClientUI

          // Dependency on TwitterLocalCache:
          val localCache = new TwitterLocalCache 

          // Dependency on TwitterService
          val service = new TwitterService
        }
Run Code Online (Sandbox Code Playgroud)

我敢肯定我一定不能错过蛋糕的优越感!但是,目前我无法看到它以任何其他方式声明依赖关系(构造函数,抽象字段).

Che*_*tEx 8

具有自我类型注释的特征比具有场注入的老式豆类更具组合性,您可能在第二个片段中考虑过这些特征.

让我们来看看你将如何实现这个特性:

val productionTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with TwitterConnection
Run Code Online (Sandbox Code Playgroud)

如果您需要测试这个特性,您可能会写:

val testTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with MockConnection
Run Code Online (Sandbox Code Playgroud)

嗯,有点DRY违规.让我们改进吧.

trait TwitterSetup extends TwitterClientComponent with TwitterUI with FSTwitterCache
val productionTwitter = new TwitterSetup with TwitterConnection
val testTwitter = new TwitterSetup with MockConnection
Run Code Online (Sandbox Code Playgroud)

此外,如果组件中的服务之间存在依赖关系(比如UI依赖于TwitterService),它们将由编译器自动解析.

  • 怎么会与构造函数注入不同?你可以有一个连接接口,并有两个实现 (2认同)

Der*_*att 7

想想如果TwitterService使用会发生什么TwitterLocalCache.如果TwitterService自我输入会更容易,TwitterLocalCache因为TwitterService无法访问val localCache你声明的内容.Cake模式(和自我键入)允许我们以更加通用和灵活的方式注入(当然,除此之外).