为什么使用 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
}
我确信我一定错过了蛋糕的优越性!但是,目前我看不到它比以任何其他方式(构造函数、抽象字段)声明依赖项提供的功能。
I have been reading about doing Dependency Injection in scala via the cake pattern. I think I understand it but I must have missed something because I still can't see the point in it! Why is it preferable to declare dependencies via self types rather than just abstract fields?
Given the example in Programming Scala TwitterClientComponent
declares dependencies like this using the cake pattern:
//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)
}
}
}
}
How is this better than declaring dependencies as abstract fields as below?
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)
}
}
}
Looking at instantiation time, which is when DI actually happens (as I understand it), I am struggling to see the advantages of cake, especially when you consider the extra keyboard typing you need to do for the cake declarations (enclosing trait)
//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
}
Now again with abstract fields, more or less the same!:
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
}
I'm sure I must be missing something about cake's superiority! However, at the moment I can't see what it offers over declaring dependencies in any other way (constructor, abstract fields).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
具有自类型注释的特征比具有字段注入的老式 bean 更具可组合性,您可能在第二个片段中想到了这一点。
让我们看看如何实例化这个特征:
如果你需要测试这个特征,你可能会写:
嗯,有点 DRY 违规。让我们改进吧。
此外,如果组件中的服务之间存在依赖关系(例如 UI 依赖于 TwitterService),编译器将自动解决它们。
Traits with self-type annotation is far more composable than old-fasioned beans with field injection, which you probably had in mind in your second snippet.
Let's look how you will instansiate this trait:
If you need to test this trait you probably write:
Hmm, a little DRY violation. Let's improve.
Furthermore if you have a dependency between services in your component (say UI depends on TwitterService) they will be resolved automatically by the compiler.
想想如果
TwitterService
使用TwitterLocalCache
会发生什么。如果TwitterService
自键入到TwitterLocalCache
会容易得多,因为TwitterService
无法访问val localCache
你已经声明了。 Cake 模式(和自键入)允许我们以更加通用和灵活的方式进行注入(当然还有其他方式)。Think about what happens if
TwitterService
usesTwitterLocalCache
. It would be a lot easier ifTwitterService
self-typed toTwitterLocalCache
becauseTwitterService
has no access to theval localCache
you've declared. The Cake pattern (and self-typing) allows for us to inject in a much more universal and flexible manner (among other things, of course).我不确定实际的接线如何工作,因此我使用您建议的抽象属性修改了您链接到的博客条目中的简单示例。
在这个简单的情况下它确实有效(所以你建议的技术确实可用)。
然而,同一篇博客至少展示了其他三种方法来达到相同的结果;我认为选择主要取决于可读性和个人喜好。在您建议的技术的情况下,恕我直言,Warmer 类无法很好地传达其注入依赖项的意图。另外,为了连接依赖项,我还必须创建另外两个特征(PotSensorMixin 和 HeaterMixin),但也许您有更好的方法来做到这一点。
I was unsure how the actual wiring would work, so I've adapted the simple example in the blog entry you linked to using abstract properties like you suggested.
in this simple case it does work (so the technique you suggest is indeed usable).
However, the same blog shows at least other three methods to achieve the same result; I think the choice is mostly about readability and personal preference. In the case of the technique you suggest IMHO the Warmer class communicates poorly its intent to have dependencies injected. Also to wire up the dependencies, I had to create two more traits (PotSensorMixin and HeaterMixin), but maybe you had a better way in mind to do it.
在这个例子中我认为没有什么大的区别。当特征声明多个抽象值时,自类型可能会带来更多的清晰度,例如
然后,您只需声明对 ThreadPool 的依赖,而不是依赖于多个抽象值。
对我来说,自我类型(如 Cake 模式中使用的)只是一次声明多个抽象成员的一种方法,并为它们提供一个方便的名称。
In this example I think there is no big difference. Self-types can potentially bring more clarity in cases when a trait declares several abstract values, like
Then instead of depending on several abstract values you just declare dependency on a ThreadPool.
Self-types (as used in Cake pattern) for me are just a way to declare several abstract members at once, giving those a convenient name.