Scala:带有抽象基类的Trait混合



我有一个抽象基类(Base),它为它(StackingTrait)定义了一些堆叠特征。

trait Base {
  def foo
}
trait StackingTrait extends Base {
  abstract override def foo { super.foo }
}

使用以下语法实现子类将非常方便,但这不起作用,因为编译器说foo需要用override声明,然后用abstract override重新编译,这是无效的,因为Impl是一个类。

class Impl extends Base with StackingTrait {
  def foo {}
}

我想不出一个好的理由为什么这样的语法不被允许;foo在逻辑上使用Impl定义,因此在概念上发生堆叠的顺序保持不变。

注意:我找到了这个解决方法,它将有效地完成我想要的相同的事情,但是helper类的必要性使我想要一个更好的解决方案。

class ImplHelper extends Base {
  def foo {}
}
class Impl extends ImplHelper with StackingTrait

为什么期望的语法不能编译,是否有一个优雅的解决方案?

我的理解是,虽然错误消息可能令人困惑,但行为是正确的。fooStackingTrait中被声明为abstract override,因此在任何混合StackingTrait的具体类中,必须在 StackingTrait之前有一个foo 的具体实现(未标记为abstract)(相对于线性化顺序)。这是因为super在线性化顺序中引用了之前的特性,所以在StackingTrait混合之前肯定需要foo的具体实现,否则super.foo将是无意义的。

当你这样做的时候:

class Impl extends Base with StackingTrait {
  def foo {}
}

线性化顺序为Base <- StackingTrait <- ImplStackingTrait之前唯一的trait是Base, Base没有定义foo的具体实现。

但是当你这样做的时候:

traitImplHelper extends Base {
  def foo {}
}
class Impl extends ImplHelper with StackingTrait

线性化顺序为:Base <- ImplHelper <- StackingTrait <- Impl这里ImplHelper包含了foo的具体定义,并且肯定在 StackingTrait之前

值得注意的是,如果您在StackingTrait之后混合了ImplHelper(如class Impl extends StackingTrait with ImplHelper),您将再次遇到相同的问题,并且它将无法编译。

所以,在我看来这是相当一致的。我不知道有什么方法可以使它像你想的那样编译。然而,如果你更关心如何更容易地编写Impl(并能够在那里定义foo,而不需要单独的类/trait),而不是编写BaseStackingTrait,你仍然可以这样做:

trait Base {
  protected def fooImpl
  def foo { fooImpl } 
}
trait StackingTrait extends Base {
  abstract override def foo { super.foo }
}
class Impl extends Base with StackingTrait {
  protected def fooImpl {}
}

就像在原始版本中一样,你强制每个具体类实现foo(以fooImpl的形式),这次它确实编译了。这里的缺点是,虽然fooImpl不能调用super.foo(这没有意义,会进入一个无限循环),但编译器不会警告您。

您可以尝试使用示例中提到的self类型来代替扩展trait。https://docs.scala-lang.org/tour/self-types.html。

最新更新