44

在 Scala 中,我已经看到了这些构造

trait T extends S

trait T { this: S =>

用于实现类似的事情(即S必须在创建实例之前定义抽象方法)。他们之间有什么区别?你为什么要使用一个而不是另一个?

4

6 回答 6

26

自类型注释允许您表达循环依赖。例如:

trait A extends B
trait B { self: A => }

这对于简单的继承是不可能的。

于 2010-02-08T21:48:09.740 回答
16

我会使用自我类型进行依赖管理:这个特征需要混合另一个特征。我会使用继承来改进另一个特征或接口。

举个例子:

trait FooService

trait FooRemoting { this : FooService => }
trait FooPersistence { this : FooService => }

object Services extends FooService with FooRemoting with FooPersistence

现在,如果 FooRemoting 和 FooPersistence 都继承自 FooService,并且 FooService 具有成员和方法,那么 Services 会是什么样子?

而对于继承,我们会有类似的东西:

trait Iterator[T] {
  def hasNext : boolean
  def next : T
}

trait InfiniteIterator[T] extends Iterator[T] {
  def hasNext = true
}
于 2010-02-08T21:46:15.500 回答
8

自从提出这个问题后,我遇到了这些帖子:

Spiros Tzavellas谈到使用 trait 作为公共接口和 self 类型作为必须由实现类混合的帮助器。

总之,如果我们想将方法实现移动到 trait 中,那么我们就有可能用支持具体方法实现并且与 trait 的主要职责无关的抽象方法污染这些 trait 的接口。解决这个问题的方法是将这些抽象方法移动到其他特征中,并使用自类型注释和多重继承将这些特征组合在一起。

例如:

trait PublicInterface { this: HelperTrait =>
  // Uses helperMethod
}

trait HelperTrait {
  def helperMethod = // ...
}

class ImplementationClass extends PublicInterface with HelperTrait

A Tour of Scala讨论了使用带有抽象类型成员的自类型注释 - 大概它不可能extend使用抽象类型成员(?)

于 2010-02-08T22:57:35.953 回答
6

我知道这个问题很老,但我想补充一些说明和例子。

特质继承和自我类型之间存在三个主要区别。

语义

继承是对象范式最耦合的关系之一,如果A扩展了B,则表示A是B。

假设我们有以下代码,

trait Animal {
  def stop():Unit = println("stop moving")
}

class Dog extends Animal {
  def bark:String = "Woof!"
}

val goodboy:Dog = new Dog

goodboy.bark
// Woof!

我们说狗动物。我们可以发送消息barkstoptogoodboy因为是狗,它理解这两种方法。

现在假设我们有一个新的特征,

trait Security {
  this: Animal =>
  def lookout:Unit = { stop(); println("looking out!") }
}

这一次 Security 不是 Animal,这很好,因为如果我们确认 Security 是 Animal 时语义上是不正确的,它们是不同的概念,可以一起使用。

所以现在我们可以创造一种新的狗,

val guardDog = new Dog with Security

guardDog.lookout
// stop moving
// looking out!

guardDog是狗,动物和安全。它理解stopbark并且lookout因为是安全的狗。

但是如果我们像这样创造一只新狗会发生什么?

val guardDog2:Dog = new Dog with Security
guardDog2.lookout // no such method!

guardDog2只是一只狗,所以我们不能调用lookout方法。(好的,这是一只带安全性的狗,但我们只看到一只狗)

循环依赖

自类型允许我们在类型之间创建循环依赖。

trait Patient {
  this: Reader =>
  def isQuite:Boolean = isReading
  def isSlow:Boolean = true
}

trait Reader {
  this: Patient =>
  def read():Unit = if(isSlow) println("Reading Slow...") else println("Reading Fast...")
  def isReading = true
}

val person = new Patient with Reader

以下代码无法编译。

trait Patient extends Reader { /** code **/}

trait Reader extends Patient { /** code **/ }

这种代码在依赖注入(蛋糕模式)中很常见。

多功能性

最后但并非最不重要的一点是,谁使用我们的特征可以决定使用它们的顺序,因此由于特征线性化,尽管使用的特征相同,但最终结果可能会有所不同。

使用正常的继承我们无法做到这一点,特征和类之间的关系是固定的。

trait Human {
  def isGoodForSports:Boolean
}

trait Programmer extends Human {
  def readStackOverflow():Unit = println("Reading...")
  override def isGoodForSports: Boolean = false
}

trait Sportsman extends Human {
  def play():Unit = println("Playing something")
  override def isGoodForSports: Boolean = true
}

val foo = new Programmer with Sportsman
foo.isGoodForSports
// true

val bar = new Sportsman with Programmer
bar.isGoodForSports
// false

希望这会很有用。

于 2018-09-08T02:58:23.907 回答
2

答案是“循环”。但不仅如此。

自类型注释为我解决了继承的基本问题:你继承的东西不能使用你现在的东西。使用 self 类型,一切都变得容易。

我的模式如下,可以认为是退化的蛋糕:

trait A { self: X => def a = reuseme}
trait B { self: X => def b = a }
class X extends A with B { def reuseme=null }

您可以在可以从程序集中的任何位置调用的多种行为中扩展您的类,同时保持干净的类型。不需要经常(并且错误地)识别蛋糕模式的痛苦间接。

在过去十年中,有一半(如果不是全部)令人费解的 Java DI 框架专门用于执行此操作,当然没有打字。仍在这个领域使用 JAVA 的人显然在浪费时间:“SCALA ouakbar”。

于 2014-12-23T22:38:10.717 回答
1

尽管它没有回答您的问题,但我试图理解自类型注释并且基本上迷失在答案中,并且以某种方式最终循环通过您的问题的变体,该问题侧重于使用自类型注释来说明依赖关系。

因此,在这里我发布了一个用例的描述,其中很好地说明了自类型注释,即类似于 'this' 作为子类型的类型安全案例:

http://programming-scala.labs.oreilly.com/ch13.html#SelfTypeAnnotationsAndAbstractTypeMembers

希望它对那些偶然发现这个问题的人有所帮助(并且,像我一样,在开始探索之前没有时间阅读 scala 书 :-))

于 2010-12-11T15:49:03.227 回答