特征继承和自我类型注释之间的区别
Posted
技术标签:
【中文标题】特征继承和自我类型注释之间的区别【英文标题】:Difference between trait inheritance and self type annotation 【发布时间】:2011-01-14 13:26:01 【问题描述】:在 Scala 中,我已经看到了构造
trait T extends S
和
trait T this: S =>
用于实现类似的事情(即必须在创建实例之前定义S
中的抽象方法)。他们之间有什么区别?为什么要使用其中一个?
【问题讨论】:
与***.com/questions/1990948/… 完全相同,这是相关列表中显示的第一个问题。 【参考方案1】:我会使用自类型进行依赖管理:这个 trait 需要混入另一个 trait。我会使用继承来优化另一个 trait 或接口。
举个例子:
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
【讨论】:
抱歉,Victor,我不明白“服务会是什么样子?”部分。我尝试了两种方法,我可以看到 Services 对象的行为相同。是什么情况让差异变得明显?【参考方案2】:自类型注释允许你表达循环依赖。例如:
trait A extends B
trait B self: A =>
这对于简单的继承是不可能的。
【讨论】:
你有用例吗? 您可以使用这种技术来模仿 C# 中的部分类。例如,请参阅msdn.microsoft.com/en-us/library/wa80x488.aspx。【参考方案3】:自从提出这个问题后,我发现了这些帖子:
Spiros Tzavellas 谈到使用 trait 作为公共接口和 self 类型作为必须由实现类混入的助手。
总之,如果我们想搬家 特征内部的方法实现 那么我们冒着污染接口的风险 那些具有抽象方法的特征 支持实施 具体方法和不相关 主要负责 特征。这个问题的解决方案是 将这些抽象方法移入 其他特征和组成特征 一起使用自类型注释 和多重继承。
例如:
trait PublicInterface this: HelperTrait =>
// Uses helperMethod
trait HelperTrait
def helperMethod = // ...
class ImplementationClass extends PublicInterface with HelperTrait
A Tour of Scala 讨论使用带有抽象类型成员的 self 类型注释 - 大概不可能 extend
抽象类型成员(?)
【讨论】:
那是倒退,不是吗?应该是“类 ImplementationClass 用 PublicInterface 扩展 HelperTrait”;也就是说,必须先混合一个特征,然后再将其引用为自我类型 在我看来这是一个糟糕的设计。辅助方法是PublicInterface
的子类的实现问题。为什么不直接使用protected
方法?【参考方案4】:
虽然它没有回答您的问题,但我试图理解自我类型注释并且基本上迷失在答案中,并且不知何故最终循环通过您的问题的变体,该问题侧重于自我类型注释的使用说明依赖关系。
所以我在这里发布一个用例的描述,其中自我类型注释得到了很好的说明,即类似于“this”作为子类型的类型安全案例:
http://programming-scala.labs.oreilly.com/ch13.html#SelfTypeAnnotationsAndAbstractTypeMembers
希望它对那些偶然发现这个问题的人有所帮助(并且,像我一样,在开始探索之前没有时间阅读 scala 书籍 :-))
【讨论】:
他们更改了链接。现在是:ofps.oreilly.com/titles/9780596155957/ApplicationDesign.html(在“自类型注释和抽象类型成员”;没有直接链接)【参考方案5】:答案是“循环”。但不仅如此。
自类型注解为我解决了继承的基本问题:你继承的东西不能使用你所是的东西。 使用 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”。
【讨论】:
【参考方案6】:我知道这个问题很老,但我想补充一些说明和例子。
trait 继承和 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!
我们说狗是动物。我们可以将消息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
只是一个 Dog,所以我们不能调用 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 read***():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
希望这能有用。
【讨论】:
以上是关于特征继承和自我类型注释之间的区别的主要内容,如果未能解决你的问题,请参考以下文章