在java世界中(更确切地说,如果你没有多重继承/ mixins),经验法则很简单:“赞成对象组合而不是类继承”。
如果您还考虑使用mixin,我想知道它是否/如何更改?特别是在scala中? mixin被认为是一种多重继承的方式,还是更多的类组合? 是否还有一个“赞成对象构成超过课程构成”(或其他方式)指南?
当人们使用(或滥用)mixins时,我已经看到了一些例子,当对象组合也可以完成这项工作时,我并不总是确定哪一个更好。在我看来,你可以用它们实现非常相似的东西,但也有一些差异,一些例子:
我知道简短的回答是“这取决于”,但可能有一些典型的情况,当这个或那个更好。
到目前为止,我可以提出一些指导原则(假设我有两个特征A和B,A想要使用B中的一些方法):
在许多情况下,mixins似乎更容易(和/或更简洁),但我很确定它们也有一些陷阱,比如“神级”和其他两篇artima文章中描述的其他:part 1 ,part 2(顺便说一句,在我看来,大多数其他问题与scala不相关/不那么严重)。
你有更多这样的提示吗?
答案 0 :(得分:39)
如果你只是将抽象特征混合到你的类定义中,然后在对象实例化时混合相应的具体特征,那么Scala中可以避免混搭的很多问题。例如
trait Locking{
// abstract locking trait, many possible definitions
protected def lock(body: =>A):A
}
class MyService{
this:Locking =>
}
//For this time, we'll use a java.util.concurrent lock
val myService:MyService = new MyService with JDK15Locking
这个结构有几个值得推荐的东西。首先,由于需要不同的特质功能组合,它可以防止类别爆炸。其次,它允许简单的测试,因为可以创建和混合“无所事事”的具体特征,类似于模拟对象。最后,我们完全隐藏了所使用的锁定特性,甚至是锁定特性,来自我们服务的消费者。
由于我们已经超越了大多数声称混合的缺点,我们仍然需要权衡 混合和组合之间。对于我自己,我通常根据假设的委托对象是否完全由包含对象封装,或者它是否可能被共享并具有自己的生命周期来做出决定。锁定提供了完全封装的委托的一个很好的例子。如果您的类使用锁定对象来管理对其内部状态的并发访问,则该锁定完全由包含对象控制,并且它及其操作都不会作为类的公共接口的一部分进行通告。对于像这样的完全封装的功能,我使用mix-ins。对于共享的内容,如数据源,请使用合成。
答案 1 :(得分:10)
您未提及的其他差异:
如果您发现特定特征最常用作其他类的父级,以便子类表现为父特征,则考虑将特征定义为类,以使此逻辑关系更清晰。
(我们说表现为,而不是是,因为前者是更精确的继承定义,基于Liskov替换原则 - 参见[Martin2003],例如。)
[Martin2003]:Robert C. Martin,敏捷软件开发:原理,模式和实践,Prentice-Hall,2003
trait
)没有构造函数参数。 因此advice, still from Programming Scala:
避免使用无法初始化为合适默认值的特征中的具体字段 使用抽象字段代替或将特征转换为带有构造函数的类 当然,无状态特征在初始化时没有任何问题。
良好的面向对象设计的一般原则是,实例应始终处于已知的有效状态,从构造过程完成时开始。
最后一部分,关于对象的 初始状态,经常帮助决定给定概念的类(和类组合)和特征(和mixins)