Stackable Traits Pattern:方法的实现“需要`抽象覆盖'修饰符”

时间:2015-09-10 21:15:15

标签: scala

最近,我发现了可堆叠的特征模式,并按照here描述的示例进行了操作。一切正常,但有一个案例我无法理解:

trait A { 
    def test : String 
}
trait B extends A { 
    // 'abstract override' modifier required as 
    // the test() method is not yet implemented
    abstract override def test = { 
        s"B${super.test}" 
    } 
}
class C extends A with B { 
    // test method concrete implementation
    override def test = { "C" } 
}

<console>:10: error: overriding method test in trait B of type => String;
method test needs `abstract override' modifiers
   class C extends A with B { override def test = { "C" } }

我无法理解为什么这不能编译,以及为什么C :: test方法需要提到的修饰符。

我注意到,通过在运行时编写C类,我可以做两个修改来进行编译:

class C extends A { override def test = { "C" } }
new C with B // works as expected

或者通过添加一个额外的类(在编译时它是相同的类):

class C extends A { 
    override def test = { "C" } 
}
class D extends C with B

new D().test
res5: String = BC

为什么我需要额外的课程(BTW扮演Basic class的角色?)

2 个答案:

答案 0 :(得分:15)

这种行为的原因是Scala的类线性化,它用于解决abstract override的歧义和语义。但首先要做的事情。

类线性化

每当你有a类型的实例A并且你在其上调用一个方法a.foobar()时,编译器必须找出在哪里找到foobar的定义。由于A可以扩展任何其他类和一组特征,因此函数foobar可能有多个定义。为了解决这些歧义,Scala将使用其所有超类和特征线性化您的类A。线性化将生成一个顺序,在该顺序中检查不同类型的foobar定义。第一个匹配将是执行的功能。

Scala规范将线性化定义为以下

  

定义5.1.2设C为具有模板C1的类,其中......具有Cn {stats}。   C,L(C)的线性化定义如下:   L(C)= C,L(Cn)+:...... +:L(C1)

     

这里+:表示连接,其中右操作数的元素替换左操作数的相同元素。

由于所有理论都是灰色的,让我们来看一个例子:

trait T1 {
  def foobar() = 1
}

trait T2 {
  def foobar() = 2
}

class B extends T2 {
  override def foobar() = 42
}

class A extends B with T1 with T2 {
  override def foobar() = super.foobar()
}

首先,我们必须覆盖类foobar中的A方法,因为我们有多个竞争定义。但是,现在问题是super.foobar调用哪个方法定义。为了找到这个,我们必须计算A的线性化。

L(A) = A, L(T2) +: L(T1) +: L(B)
L(B) = B, L(T2)
L(T2) = T2
L(T1) = T1
L(A) = A, T2 +: (T1, B, T2)
L(A) = A, T1, B, T2

因此,super.foobar会在T1中调用返回1的定义。

抽象覆盖

方法的abstract override修饰符基本上表示必须有一个类/特征I实现此方法,该方法在类线性化中具有abstract override修饰符的特征之后出现你实例化的类。为了执行super.foobar(),这是必要的,因为super.foobar()需要进一步搜索线性化以查找foobar的定义。

当您现在查看类C的定义时,您会发现它具有以下线性化

 C, B, A

因此,它无法编译,因为从B开始,您找不到test的实现。

当我们现在看看有效的例子时,我们就会知道它们实际工作的原因。对于C extends A new C with B的情况,您基本上创建了一个匿名类Z extends C with BZ的线性化是

Z, B, C, A

您可以看到,B可以在C中找到test的实现。因此,代码可以编译。对于类D的示例也是如此。

答案 1 :(得分:1)

根据article you provided

基本特征(或抽象类)定义了所有核心和可堆叠扩展的抽象接口,如图1所示。核心特征(或类)实现基本特征中定义的抽象方法,并提供基本的核心功能。每个stackable使用Scala的抽象覆盖修饰符覆盖基本特征中定义的一个或多个抽象方法,并提供一些行为,并在某些时候调用相同方法的超级实现。以这种方式,可堆叠组件修改它们被混合到的任何核心的行为。

在你的情况下:

 class C extends A with B { override def test = { "C" } }

你没有核心特征。 A是基础,因为它定义了接口,B是可堆叠的(因为它调用super,期望它在核心中实现),{{1也是 stackable ,因为类的主体中的C声明是最具体的(它覆盖了所有特征中的一个)。

在你的&#34;固定&#34;您刚刚介绍了正确的核心实现的示例:

test

class C extends A { override def test = { "C" } } new C with B // works as expected class C extends A { override def test = { "C" } } class D extends C with B C在被test覆盖之前定义B,因此它用作核心

相关问题