谷歌搜索后,出现了许多有关该主题的回复。但是,我觉得它们中的任何一个都无法很好地说明这两个功能之间的区别。所以我想再尝试一次,特别是...
使用自我类型而不是继承可以做什么,反之亦然?
对我来说,两者之间应该有一些可量化的物理差异,否则它们只是名义上的差异。
如果特征A扩展了B或自类型B,它们是否都说明存在B是必要条件?区别在哪里?
谷歌搜索后,出现了许多有关该主题的回复。但是,我觉得它们中的任何一个都无法很好地说明这两个功能之间的区别。所以我想再尝试一次,特别是...
使用自我类型而不是继承可以做什么,反之亦然?
对我来说,两者之间应该有一些可量化的物理差异,否则它们只是名义上的差异。
如果特征A扩展了B或自类型B,它们是否都说明存在B是必要条件?区别在哪里?
Answers:
如果特征A扩展了B,则在A中混合会给您精确的 B加上A所添加或扩展的任何东西。相反,如果特征A具有一个明确引用为B的自引用,则最终的父类也必须混入B 或 B 的后代类型(并将其混入first,这很重要)。
那是最重要的区别。在第一种情况下,B的精确类型在其延伸的点A处结晶。在第二个中,父类的设计者在组成父类的点上决定使用哪个版本的B。
另一个区别是A和B提供相同名称的方法。在A扩展B的情况下,A的方法将覆盖B的方法。在B后面混入A的情况下,A的方法简单获胜。
有类型的自我参考会给您更多自由。A和B之间的耦合松动。
更新:
由于您不清楚这些差异的好处...
如果使用直接继承,则将创建特征A,即B + A。您已将关系定下来。
如果您使用类型化的自我参照,那么想要在C类中使用您的特征A的任何人都可以
考虑到Scala允许您使用代码块作为构造函数直接实例化特征,因此这不是其选择的限制。
至于A的方法获胜之间的差异,因为A的混合程度最高,所以与A扩展的B相比,请考虑...
在混合了一系列特征的地方,每当foo()
调用方法时,编译器都会进入混合的最后一个特征以寻找foo()
,然后(如果未找到)遍历左侧的序列,直到找到实现foo()
并使用的特征那。A还可以选择调用super.foo()
,它还会遍历左侧的序列,直到找到实现为止,依此类推。
因此,如果A具有对B的类型化自引用,并且A的编写者知道B实现了foo()
,则A可以在super.foo()
知道如果没有其他提供的情况下调用foo()
B。但是,类C的创建者可以选择删除实现的任何其他特性foo()
,而A会代替它。
再次,这比A扩展B并直接调用B的版本要强大得多,限制也更少foo()
。
我有一些代码说明了扩展与设置自类型时在可见性和“默认”实现方面的一些差异。它没有说明已经讨论过的有关如何解决实际名称冲突的任何部分,而是着重于可能做和不可能做的事情。
trait A1 {
self: B =>
def doit {
println(bar)
}
}
trait A2 extends B {
def doit {
println(bar)
}
}
trait B {
def bar = "default bar"
}
trait BX extends B {
override def bar = "bar bx"
}
trait BY extends B {
override def bar = "bar by"
}
object Test extends App {
// object Thing1 extends A1 // FAIL: does not conform to A1 self-type
object Thing1 extends A1 with B
object Thing2 extends A2
object Thing1X extends A1 with BX
object Thing1Y extends A1 with BY
object Thing2X extends A2 with BX
object Thing2Y extends A2 with BY
Thing1.doit // default bar
Thing2.doit // default bar
Thing1X.doit // bar bx
Thing1Y.doit // bar by
Thing2X.doit // bar bx
Thing2Y.doit // bar by
// up-cast
val a1: A1 = Thing1Y
val a2: A2 = Thing2Y
// println(a1.bar) // FAIL: not visible
println(a2.bar) // bar bx
// println(a2.bary) // FAIL: not visible
println(Thing2Y.bary) // 42
}
IMO的一个重要区别是,A1
它不会暴露B
任何仅将其视为事物的需求A1
(如上部分所示)。唯一会真正看到使用特定特化B
形式的代码是明确知道组合类型的代码(例如Think*{X,Y}
)。
另一点是,如果未指定其他任何内容,则A2
(带有扩展名)将实际使用B
,而A1
(自类型)未声明B
除非覆盖,否则将实际使用(在实例化对象时必须显式给出具体的B)。