自我类型和特质子类有什么区别?
A型的自我型:
trait B trait A { this: B => }
说“ A
不能混入一个不扩展B
的具体类” 。
另一方面,以下是:
trait B trait A extends B
“ A
混合的任何(具体的或抽象的)类也将在B中混合” 。
这两个陈述不是这个意思吗? 自我类型似乎只会造成编译时错误的可能性。
我错过了什么?
它主要用于依赖注入 ,比如Cake模式 。 在Scala中有很多不同形式的依赖注入,包括Cake模式。 如果你是谷歌的“蛋糕模式和斯卡拉”,你会得到很多链接,包括演示文稿和视频。 现在,这里是另一个问题的链接。
现在,关于自我类型和扩展特质之间的区别是什么,这很简单。 如果你说B extends A
,那么B
是 A
当你使用自我类型时, B
需要一个A
有两种自我类型创建的具体要求:
- 如果
B
延长,那么你需要混入一个A
- 当一个具体的类最终扩展/混合这些特性时,一些类/特性必须实现
A
考虑下面的例子:
scala> trait User { def name: String } defined trait User scala> trait Tweeter { | user: User => | def tweet(msg: String) = println(s"$name: $msg") | } defined trait Tweeter scala> trait Wrong extends Tweeter { | def noCanDo = name | } <console>:9: error: illegal inheritance; self-type Wrong does not conform to Tweeter's selftype Tweeter with User trait Wrong extends Tweeter { ^ <console>:10: error: not found: value name def noCanDo = name ^
如果Tweeter
是User
一个子类,则不会有错误。 在上面的代码中,每当使用Tweeter
都要求 User
,但是User
没有提供Wrong
,所以我们得到了一个错误。 现在,上面的代码仍然在范围内,请考虑:
scala> trait DummyUser extends User { | override def name: String = "foo" | } defined trait DummyUser scala> trait Right extends Tweeter with User { | val canDo = name | } defined trait Right scala> trait RightAgain extends Tweeter with DummyUser { | val canDo = name | } defined trait RightAgain
使用Right
,满足了混合User
的要求。 但是,上面提到的第二个要求并没有得到满足:实现User
的负担仍然是延伸Right
类/特性。
与RightAgain
两个要求都满意。 提供User
和User
的实现。
有关更多实际用例,请参阅本答复开始处的链接! 但是,希望现在你明白了。
自我类型允许您定义循环依赖。 例如,你可以做到这一点:
trait A { self: B => } trait B { self: A => }
继承使用extends
不允许。 尝试:
trait A extends B trait B extends A error: illegal cyclic reference involving trait A
在Odersky书中,请参阅第33.5节(创建电子表格用户界面章节),其中提到:
在电子表格示例中,类Model继承自Evaluator,从而获得对其评估方法的访问权限。 换一种方式,类Evaluator将其自我类型定义为Model,如下所示:
package org.stairwaybook.scells trait Evaluator { this: Model => ...
希望这可以帮助。
另一个区别是自我类型可以指定非类类型。 例如
trait Foo{ this: { def close:Unit} => ... }
这里的自我类型是一个结构类型。 结果就是说,在Foo中混合的任何东西都必须实现一个无参数“close”方法的返回单元。 这允许安全混合鸭子打字。
Martin Odersky最初的Scala论文2.3节的“自我类型注释” 可伸缩组件抽象实际上很好地解释了混合组合以外的自我类型的目的:提供将类与抽象类型关联的另一种方式。
本文给出的例子如下所示,它似乎没有一个优雅的子类记者:
abstract class Graph { type Node <: BaseNode; class BaseNode { self: Node => def connectWith(n: Node): Edge = new Edge(self, n); } class Edge(from: Node, to: Node) { def source() = from; def target() = to; } } class LabeledGraph extends Graph { class Node(label: String) extends BaseNode { def getLabel: String = label; def self: Node = this; } }
让我们从循环依赖开始吧。
trait A { selfA: B => def fa: Int } trait B { selfB: A => def fb: String }
然而,这个解决方案的模块性不如它最先出现的那么好,因为你可以覆盖自我类型,如下所示:
trait A1 extends A { selfA1: B => override def fb = "B's String" } trait B1 extends B { selfB1: A => override def fa = "A's String" } val myObj = new A1 with B1
虽然,如果您重写自我类型的成员,但是您将失去对原始成员的访问权限,但仍然可以通过超级继承来访问原始成员。 那么使用继承真正获得的是:
trait AB { def fa: String def fb: String } trait A1 extends AB { override def fa = "A's String" } trait B1 extends AB { override def fb = "B's String" } val myObj = new A1 with B1
现在我不能理解蛋糕模式的所有细微之处,但是让我感到强制模块化的主要方法是通过组合而不是继承或自我类型。
继承版本更短,但我更喜欢继承自我类型的主要原因是,我发现使用自我类型来获得初始化顺序更为棘手。 然而,有些事情你可以用自我类型做,而你不能用继承来做。 自我类型可以使用一个类型,而继承需要特质或类,如下所示:
trait Outer { type T1 } trait S1 { selfS1: Outer#T1 => } //Not possible with inheritance.
你甚至可以这样做:
trait TypeBuster { this: Int with String => }
虽然你永远不能实例化它。 我没有看到任何不能从类型继承的绝对理由,但我肯定觉得有道路构造函数类和特征,因为我们有类型构造函数特征/类。 不幸的是
trait InnerA extends Outer#Inner //Doesn't compile
我们有这个:
trait Outer { trait Inner } trait OuterA extends Outer { trait InnerA extends Inner } trait OuterB extends Outer { trait InnerB extends Inner } trait OuterFinal extends OuterA with OuterB { val myV = new InnerA with InnerB }
或这个:
trait Outer { trait Inner } trait InnerA {this: Outer#Inner =>} trait InnerB {this: Outer#Inner =>} trait OuterFinal extends Outer { val myVal = new InnerA with InnerB with Inner }
应该同情更多的一点是,特质可以扩展类。 感谢David Maclver指出这一点。 以下是我自己的代码示例:
class ScnBase extends Frame abstract class ScnVista[GT <: GeomBase[_ <: TypesD]](geomRI: GT) extends ScnBase with DescripHolder[GT] ) { val geomR = geomRI } trait EditScn[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT] trait ScnVistaCyl[GT <: GeomBase[_ <: ScenTypes]] extends ScnVista[GT]
ScnBase
继承于Swing框架类,所以它可以被用作一个自我类型,然后在最后(实例化)混合。 然而, val geomR
在被继承特性使用之前需要被初始化。 所以我们需要一个类来强制事先初始化geomR
。 ScnVista
类可以通过多个可以自己继承的正交特征继承。 使用多个类型参数(泛型)提供了另一种模块化形式。
另一件没有提到的事情是,因为自我类型不是所需类的层次结构的一部分,所以它们可以从模式匹配中排除,特别是当你对一个密封层次进行彻底匹配时。 如果要对正交行为进行建模,这很方便,例如:
sealed trait Person trait Student extends Person trait Teacher extends Person trait Adult { this : Person => } // orthogonal to its condition val p : Person = new Student {} p match { case s : Student => println("a student") case t : Teacher => println("a teacher") } // that's it we're exhaustive
TL; DR其他答案的总结:
-
你扩展的类型暴露于继承类型,但是自我类型不是
例如:
class Cow { this: FourStomachs }
允许您使用仅适用于反刍动物的方法,如digestGrass
。 然而,延长牛的特性将没有这种特权。 另一方面,class Cow extends FourStomachs
将揭露digestGrass
给extends Cow
任何人。 -
自我类型允许循环依赖,扩展其他类型不
trait A { def x = 1 } trait B extends A { override def x = super.x * 5 } trait C1 extends B { override def x = 2 } trait C2 extends A { this: B => override def x = 2} // 1. println((new C1 with B).x) // 2 println((new C2 with B).x) // 10 // 2. trait X { type SomeA <: A trait Inner1 { this: SomeA => } // compiles ok trait Inner2 extends SomeA {} // doesn't compile }
自我类型让你指定什么类型可以混入特质。 例如,如果您有一个自我类型为Closeable
,则该特征知道允许混合的特征必须实现可Closeable
界面。
更新:一个主要的区别是,自我类型可以依赖于多个类(我承认这是一个角落的情况下)。 例如,你可以拥有
class Person { //... def name: String = "..."; } class Expense { def cost: Int = 123; } trait Employee { this: Person with Expense => // ... def roomNo: Int; def officeLabel: String = name + "/" + roomNo; }
这允许将Employee
mixin添加到Person
和Expense
的子类中的任何东西。 当然,这只有在Expense
扩展到Person
时才有意义,反之亦然。 关键是使用自我类型Employee
可以独立于它所依赖的类的层次结构。 它不关心什么扩展了什么 – 如果您切换Expense
与Person
的层次结构,您不必修改Employee
。
在第一种情况下,B的一个子特性或子类型可以被混合到任何用途中。所以B可以是一个抽象的特征。