特征继承和自我类型注释之间的区别

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!

我们说狗动物。我们可以将消息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 只是一个 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

希望这能有用。

【讨论】:

以上是关于特征继承和自我类型注释之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

JAVA类和对象 自我总结

小白自我提高学习设计模式笔记—装饰者模式

记录TS和JS的区别

记录TS和JS的区别

“注释”是程序员的自我修养

从C#中的接口继承XML注释