我一直在阅读通过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
}
我确信我一定不能错过蛋糕的优越感!但是,目前我无法看到它以任何其他方式声明依赖关系(构造函数,抽象字段)。
答案 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模式中使用)只是一种声明几个抽象成员的方法,给出了一个方便的名称。