7

似乎 Scala显式类型的自引用最常见的用法是在“蛋糕模式”中,其中模块的依赖项声明如下:

class Foo { this: A with B with C =>
  // ...
}

一般来说,暂时忽略蛋糕模式,,A可以引用任何类型级别的东西,例如类型参数:BC

class Outer[A, B, C] {
  class Inner { this: A with B with C =>
    // ...
  }
}

...或抽象类型成员:

class Outer {
  type A
  type B
  type C
  class Inner { this: A with B with C =>
    // ...
  }
}

在这两种情况下,我们都不能写,abstract class Inner extends A with B with C因为和不知道是特征。这里需要显式类型的自引用。但是,我只见过带有特征的蛋糕图案:ABC

trait A { def a }
trait B { def b }
trait C { def c }
class Foo { this: A with B with C =>
  // ...
}

在这种情况下,我们可以abstract class Foo extends A with B with C直接写,如果我没记错的话,意思是一样的。我对么?如果不是,那么它们有何不同;如果是这样,为什么每个人似乎都使用显式类型的自我引用?

4

2 回答 2

6

我似乎忽略了两个主要区别:

  1. 尽管显式 self-type 注释和 simpleextends关键字都描述了两种类型之间的“is-a”关系,但在前一种情况下,这种关系在外部是不可见的:

    scala> trait T
    defined trait T
    
    scala> class C { this: T => }
    defined class C
    
    scala> implicitly[C <:< T]
    <console>:10: error: Cannot prove that C <:< T.
    

    这是一件好事,因为在蛋糕模式中,您不希望您的“模块”对象在不经意间被多态地用作它所依赖的特征之一。

  2. 正如 MushtaqDaniel 间接指出的那样,在使用自类型注释时,依赖关系可以是循环的。循环依赖很常见,而且不一定是坏事(假设相互依赖的组件不需要彼此进行初始化,或者我们可以以某种方式在它们之间打结),因此这是自类型注释相对于继承的另一个明显好处。

于 2012-07-13T19:46:35.070 回答
1

当您使用继承时,您将决定初始化顺序。当您使用自我类型时,您将其保持打开状态。

还有其他差异,但我认为其中大部分是实现细节,可能会消失。我知道其中一些是。

于 2012-07-13T19:15:09.083 回答