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

时间:2011-08-10 13:59:02

标签: scala dependency-injection cake-pattern

我一直在阅读通过cake pattern在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实际发生的时间(据我所知),我很难看到蛋糕的优点,特别是考虑到你需要为蛋糕声明做的额外键盘输入(封闭特性) )

    //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 个答案:

答案 0 :(得分:8)

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

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

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),它们将由编译器自动解析。

答案 1 :(得分:7)

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

答案 2 :(得分: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),但也许你有更好的方法来做它。

答案 3 :(得分:1)

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

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

然后,您只需声明对ThreadPool的依赖,而不是依赖于几个抽象值。 对我来说,自我类型(在Cake模式中使用)只是一种声明几个抽象成员的方法,给出了一个方便的名称。