特质继承和自身类型注释之间的区别

43
在Scala中,我看到了以下构造方式:
trait T extends S

并且。
trait T { this: S =>

这两者都可以实现类似的功能(即在创建实例之前必须定义S中的抽象方法)。它们的区别是什么?为什么要选择其中之一?


9
这是一个重复的问题,与相关列表中显示的第一个问题 https://dev59.com/enI-5IYBdhLWcg3wKE-x 相同。 - Daniel C. Sobral
6个回答

27

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

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

简单的继承方式无法实现这一点。


2
你有使用案例吗? - crak
您可以使用此技术来模仿C#中的部分类。例如,请参见https://msdn.microsoft.com/en-us/library/wa80x488.aspx。 - Joa Ebert

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
}

25
抱歉 Victor,我不明白“服务会是什么样子?”这部分的意思。我尝试了两种方式,但我看到 Services 对象的行为是相同的。什么情况会使这种差异显现出来? - mkm

8

我知道这个问题是老问题了,但我想添加一些澄清和示例。

特质继承和self类型有三个主要的区别。

语义

继承是面向对象范式中耦合度最高的关系之一,如果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!

我们说狗是动物。因为狗是动物,所以我们可以向goodboy发送“bark”和“stop”消息,它能理解这两种方法。现在假设我们有一个新的特征,
trait Security {
  this: Animal =>
  def lookout:Unit = { stop(); println("looking out!") }
}

这次安全不是动物,这很好,因为如果我们肯定安全是动物,那在语义上是不正确的,它们是可以一起使用的不同概念。所以现在我们可以创造一种新的狗。
val guardDog = new Dog with Security

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

guardDog 是一只狗,同时也是动物和安全工具。它懂得停下(stop)、叫声(bark)和警戒(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 **/ }

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

多功能性

最后,使用我们的traits的人可以决定它们被使用的顺序,因此由于Trait线性化,最终结果可能会有所不同,尽管使用的traits相同。

使用普通继承无法做到这一点,traits和类之间的关系是固定的。

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

希望这对您有所帮助。


8

自从提出这个问题后,我看到了以下几篇文章:

Spiros Tzavellas谈到了使用特质作为公共接口和自身类型作为必须由实现类混合的辅助类。

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

例如:

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

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

class ImplementationClass extends PublicInterface with HelperTrait

Scala之旅讨论了使用自类型注释和抽象类型成员 - 大概不可能extend一个抽象类型成员(?)


这是反过来了,不是吗?应该是“class ImplementationClass extends HelperTrait with PublicInterface”,也就是说,在引用自身类型之前必须先混入 trait。 - virtualeyes
1
这对我来说似乎是一个糟糕的设计。帮助方法是PublicInterface子类的实现关注点。为什么不使用protected方法呢? - Robin Green

2
答案是"循环性"。但不只这样。
自类型注释解决了我在继承中面临的根本问题:你继承自的对象不能使用你自己。有了自类型,一切变得简单。
我的模式如下,可以被认为是一个退化的蛋糕:
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”。

1

虽然这并没有回答你的问题,但我一直在尝试理解自类型注释,最终在各种答案中迷失了方向,不知怎么就陷入了循环,反复思考了你关于使用自类型注释来说明依赖关系的问题。

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

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

希望这对那些偶然看到这个问题的人有所帮助(就像我一样,在开始探索之前没有时间阅读Scala书籍 :-) )

他们已经更改了链接。现在是:http://ofps.oreilly.com/titles/9780596155957/ApplicationDesign.html(位于“Self-Type Annotations and Abstract Type Members”下;没有直接链接)。 - akauppi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接