特质继承与自我类型注释的区别

时间:2010-02-08 21:17:20

标签: scala composition traits self-type

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

trait T extends S

trait T { this: S =>

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

6 个答案:

答案 0 :(得分:26)

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

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

简单继承无法做到这一点。

答案 1 :(得分:14)

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

仅作为一个例子:

trait FooService

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

object Services extends FooService with FooRemoting with FooPersistence

现在,如果FooRemoting和FooPersistence都继承自FooService,而FooService有成员和方法,那么服务会是什么样子?

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

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

trait InfiniteIterator[T] extends Iterator[T] {
  def hasNext = true
}

答案 2 :(得分:8)

自从提出问题后,我发现了这些帖子:

Spiros Tzavellas讨论了如何将特征用作公共接口,将self类型作为帮助器,必须由实现类混合使用。

  

总之,如果我们想要搬家   特征内部的方法实现   然后我们冒着污染界面的风险   那些具有抽象方法的特征   支持执行   具体方法和无关   主要负责人   特征。解决这个问题的方法是   移动那些抽象方法   其他特征和构成特征   一起使用自我类型注释   和多重继承。

例如:

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

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

class ImplementationClass extends PublicInterface with HelperTrait

A Tour of Scala讨论了使用抽象类型成员的自我类型注释 - 大概是extend抽象类型成员(?)

答案 3 :(得分: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”。

答案 4 :(得分:2)

我知道这个问题很旧,但是我想补充一些澄清和例子。

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

语义学

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

我们是说狗是动物。我们可以向bark发送消息stopgoodboytrait Security { this: Animal => def lookout:Unit = { stop(); println("looking out!") } } ,因为它是一条狗,它可以理解这两种方法。

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

val guardDog = new Dog with Security

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

这次,安全性不是动物,这很好,因为如果我们确认安全性是动物,那么它们在语义上是不正确的,它们是不同的概念,可以一起使用。

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

guardDog

stop是狗,动物和安全。它了解barklookoutval 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

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

多功能

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

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

MOVQ 0x30(SP), DX

希望这会很有用。

答案 5 :(得分:1)

虽然它没有回答你的问题,但我试图理解自我类型的注释并且基本上在答案中迷失了,并且不知何故最终在你的问题的变体中循环,其重点是使用自我类型的注释陈述依赖。

所以在这里我发布了一个用例的描述,其中自我类型注释得到了很好的说明,即类似安全的'this'作为子类型的情况:

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

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