在Scala中,我已经看到了构造
trait T extends S
和
trait T { this: S =>
用于实现类似的事情(即必须在创建实例之前定义S
中的抽象方法)。他们之间有什么区别?你为什么要用一个呢?
答案 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
发送消息stop
和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
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 **/ }
以下代码无法编译。
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书:-))