Scala中的自我类型和特质继承有什么区别?


9

谷歌搜索后,出现了许多有关该主题的回复。但是,我觉得它们中的任何一个都无法很好地说明这两个功能之间的区别。所以我想再尝试一次,特别是...

使用自我类型而不是继承可以做什么,反之亦然?

对我来说,两者之间应该有一些可量化的物理差异,否则它们只是名义上的差异。

如果特征A扩展了B或自类型B,它们是否都说明存在B是必要条件?区别在哪里?


我对您对赏金所设定的条款持谨慎态度。一方面,定义“物理”差异,因为这都是软件。除此之外,对于任何使用mixins创建的复合对象,如果纯粹根据可见方法定义函数,则可以在函数中创建具有继承关系的近似对象。它们的不同之处在于可扩展性,灵活性和可组合性。
itsbruce 2013年

如果您有各种不同尺寸的钢板,则可以将它们用螺栓固定在一起以形成盒子,也可以焊接它们。从一个狭义的角度来看,这些功能在功能上是等效的-如果您忽略了一个事实可以轻松地重新配置或扩展而另一个却不能轻松配置的事实。我有种感觉,您可能会争辩说它们是等效的,尽管如果您多说一些标准,我很乐意被证明是错误的。
itsbruce

我对您通常所说的完全不熟悉,但是我仍然不了解这种特殊情况的区别。您能否提供一些代码示例来说明一种方法比另一种方法更具扩展性和灵活性?*具有扩展名的基本代码*具有自我类型的基本代码*向扩展样式添加的功能*向自我类型样式添加的功能
Mark Canlas

好吧,我想我可以在赏金用尽之前尝试一下;)
itsbruce

Answers:


11

如果特征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的任何人都可以

  • 混合B,然后将A混合为C。
  • 将B的子类型混合,然后将A混合为C。
  • 将A混合到C,其中C是B的子类。

考虑到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()


A获胜与A超越之间的功能区别是什么?我在两种情况下都通过不同的机制获得了A?在第一个示例中……在第一段中,为什么不让特征A扩展SuperOfB?感觉就像我们总是可以使用任何一种机制来重塑问题一样。我想我没有看到不可能的用例。或者我承担太多事情。
Mark Canlas

嗯,你为什么会有一个延长B的子类,如果B定义你需要什么?自引用强制存在B(或子类),但给开发人员选择了吗?只要可以扩展特征B,它们就可以混合在编写特征A之后编写的内容。为什么只将它们限制为编写特征A时可用的内容?
itsbruce

已更新,以使差异变得更加清晰。
itsbruce

@itsbruce在概念上有区别吗?IS-A与HAS-A?
2014年

@Jas在特征A和特征B之间的关系的上下文中,继承是IS-A,而键入的自引用给出了HAS-A(组成关系)。对于混合了特征的类,结果为IS-A,无论如何。
itsbruce 2014年

0

我有一些代码说明了扩展与设置自类型时在可见性和“默认”实现方面的一些差异。它没有说明已经讨论过的有关如何解决实际名称冲突的任何部分,而是着重于可能做和不可能做的事情。

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)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.