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

问题描述 投票:41回答:6

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

trait T extends S

trait T { this: S =>

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

scala composition traits self-type
6个回答
14
投票

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

举个例子:

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
}

26
投票

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

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

简单的继承是不可能的。


8
投票

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

Spiros Tzavellas谈到使用特征作为公共接口和自我类型作为帮助器,必须由实现类混合。

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

例如:

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

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

class ImplementationClass extends PublicInterface with HelperTrait

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


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”。


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!

我们说狗是动物。我们可以将消息barkstop发送到goodboy,因为它是一只狗,它理解这两种方法。

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

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

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

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

val guardDog = new Dog with Security

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

guardDog是一只狗,动物和安全。它了解stopbarklookout,因为它是一只有安全的狗。

但是,如果我们创造一个这样的新狗,会发生什么?

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

guardDog2只是一只狗,所以我们不能称之为lookout方法。 (okok,这是一只安全的狗,但我们只看到一只狗)

循环依赖

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

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线性化,最终结果可能不同,尽管使用的特征是相同的。

正常继承我们不能这样做,特征和类之间的关系是固定的。

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

希望这可能有用。


1
投票

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.