25

我一直在阅读有关通过蛋糕模式在 scala 中进行依赖注入的信息。我想我明白了,但我一定错过了什么,因为我仍然看不到其中的意义!为什么最好通过自身类型而不是抽象字段来声明依赖关系?

鉴于Programming Scala TwitterClientComponent中的示例使用蛋糕模式声明这样的依赖项:

//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)
      }
    }
  }
}

这比将依赖项声明为如下抽象字段有什么好处?

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)
    }
  }
}

查看实例化时间,即 DI 实际发生的时间(据我所知),我很难看到 cake 的优势,特别是当您考虑需要为 cake 声明(封闭特征)做的额外键盘输入时

    //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
    }

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

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
        }

我敢肯定我一定错过了蛋糕的优越性!但是,目前我看不到它以任何其他方式(构造函数、抽象字段)声明依赖项提供了什么。

4

4 回答 4

8

具有自我类型注释的特征比具有字段注入的老式 bean 更易于组合,您可能在第二个片段中已经想到了这一点。

让我们看看你将如何实例化这个特性:

val productionTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with TwitterConnection

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

val testTwitter = new TwitterClientComponent with TwitterUI with FSTwitterCache with MockConnection

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

trait TwitterSetup extends TwitterClientComponent with TwitterUI with FSTwitterCache
val productionTwitter = new TwitterSetup with TwitterConnection
val testTwitter = new TwitterSetup with MockConnection

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

于 2011-08-10T19:49:29.537 回答
7

想想如果TwitterService使用TwitterLocalCache. TwitterService如果自我键入会容易得多,TwitterLocalCache因为TwitterService无法访问val localCache您声明的。Cake 模式(和自键入)允许我们以更加通用和灵活的方式进行注入(当然还有其他)。

于 2011-08-10T14:30:13.473 回答
1

在这个例子中,我认为没有太大的区别。当一个 trait 声明了几个抽象值时,自我类型可能会带来更多的清晰度,比如

trait ThreadPool {
  val minThreads: Int
  val maxThreads: Int
}

然后,您只需声明对 ThreadPool 的依赖,而不是依赖于几个抽象值。对我来说,self-types(在 Cake 模式中使用)只是一次声明多个抽象成员的一种方式,给它们一个方便的名称。

于 2013-10-10T21:35:18.343 回答
1

我不确定实际的接线是如何工作的,所以我已经修改了你链接到的博客条目中的简单示例,使用你建议的抽象属性。

// =======================  
// service interfaces  
trait OnOffDevice {  
  def on: Unit  
  def off: Unit  
}  
trait SensorDevice {  
  def isCoffeePresent: Boolean  
}  

// =======================  
// service implementations  
class Heater extends OnOffDevice {  
  def on = println("heater.on")  
  def off = println("heater.off")  
}  
class PotSensor extends SensorDevice {  
  def isCoffeePresent = true  
}  

// =======================  
// service declaring two dependencies that it wants injected  
// via abstract fields
abstract class Warmer() {
  val sensor: SensorDevice   
  val onOff: OnOffDevice  

  def trigger = {  
    if (sensor.isCoffeePresent) onOff.on  
    else onOff.off  
  }  
}  

trait PotSensorMixin {
    val sensor = new PotSensor
}

trait HeaterMixin {
    val onOff = new Heater  
}

 val warmer = new Warmer with PotSensorMixin with HeaterMixin
 warmer.trigger 

在这个简单的情况下它确实有效(所以你建议的技术确实是可用的)。

但是,同一篇博客至少展示了其他三种方法来达到相同的结果;我认为选择主要是关于可读性和个人喜好。在您建议恕我直言的技术的情况下,Warmer 类传达其意图注入依赖项的意图很差。此外,为了连接依赖关系,我必须创建另外两个特征(PotSensorMixin 和 HeaterMixin),但也许你有更好的方法来做到这一点。

于 2011-08-10T15:14:37.513 回答