在 Scala 中,我已经看到了这些构造
trait T extends S
和
trait T { this: S =>
用于实现类似的事情(即S
必须在创建实例之前定义抽象方法)。他们之间有什么区别?你为什么要使用一个而不是另一个?
在 Scala 中,我已经看到了这些构造
trait T extends S
和
trait T { this: S =>
用于实现类似的事情(即S
必须在创建实例之前定义抽象方法)。他们之间有什么区别?你为什么要使用一个而不是另一个?
自类型注释允许您表达循环依赖。例如:
trait A extends B
trait B { self: A => }
这对于简单的继承是不可能的。
我会使用自我类型进行依赖管理:这个特征需要混合另一个特征。我会使用继承来改进另一个特征或接口。
举个例子:
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
}
自从提出这个问题后,我遇到了这些帖子:
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
使用抽象类型成员(?)
我知道这个问题很老,但我想补充一些说明和例子。
特质继承和自我类型之间存在三个主要区别。
继承是对象范式最耦合的关系之一,如果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
togoodboy
因为是狗,它理解这两种方法。
现在假设我们有一个新的特征,
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
是狗,动物和安全。它理解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
希望这会很有用。
答案是“循环”。但不仅如此。
自类型注释为我解决了继承的基本问题:你继承的东西不能使用你现在的东西。使用 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”。
尽管它没有回答您的问题,但我试图理解自类型注释并且基本上迷失在答案中,并且以某种方式最终循环通过您的问题的变体,该问题侧重于使用自类型注释来说明依赖关系。
因此,在这里我发布了一个用例的描述,其中很好地说明了自类型注释,即类似于 'this' 作为子类型的类型安全案例:
http://programming-scala.labs.oreilly.com/ch13.html#SelfTypeAnnotationsAndAbstractTypeMembers
希望它对那些偶然发现这个问题的人有所帮助(并且,像我一样,在开始探索之前没有时间阅读 scala 书 :-))