一个用于特质A
的自类型:
trait B
trait A { this: B => }
说的是:"A
不能被混合到没有扩展 B
的具体类中。"
另一方面,以下内容:
trait B
trait A extends B
它表明 "任何(具体或抽象)混合 A
的类也将混合 B"。
这两个语句不是意思相同的吗?Self-type 似乎只是为了创建一种简单的编译时错误的可能性。
我错过了什么吗?
它主要用于依赖注入,例如在Cake Pattern中。有一篇很棒的文章涵盖了Scala中许多不同形式的依赖注入,包括Cake Pattern。如果你在Google上搜索"Cake Pattern and Scala",你会得到许多链接,包括演示和视频。现在,这是一个指向另一个问题的链接。
现在,自类型和扩展特质之间的区别很简单。如果你说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
的要求。但是,上述第二个要求未得到满足:仍需为扩展 Right
的类/特质实现 User
。RightAgain
,两个要求均已得到满足。提供了 User
和 User
的实现。trait WarmerComponentImpl extends SensorDeviceComponent with OnOffDeviceComponent
这样吗?这将会让WarmerComponentImpl
拥有那些接口。它们会对任何扩展了WarmerComponentImpl
的内容都是可用的,这显然是错误的,因为它_不是_SensorDeviceComponent
或者OnOffDeviceComponent
。作为自类型,这些依赖项仅对WarmerComponentImpl
_独占_。一个List
可以被用作一个Array
,反之亦然。但它们并不是完全相同的东西。 - Daniel C. Sobralthis
是我不赞成的做法,因为它毫无意义地掩盖了原始的 this
。 - Daniel C. Sobralself: Dep1 with Dep2 =>
。 - Daniel C. Sobral自类型允许您定义循环依赖关系。例如,您可以实现以下内容:
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节(创建电子表格UI章节),其中提到:
在电子表格示例中,Model类继承自Evaluator并因此获得了访问其evaluation方法的权限。要实现相反的效果,Evaluator类定义了其自身类型为Model,像这样:package org.stairwaybook.scells
trait Evaluator { this: Model => ...
另一个区别是自类型可以指定非类类型。例如:
trait Foo{
this: { def close:Unit} =>
...
}
这里的self类型是一个结构类型。作用是说任何混入Foo的内容都必须实现一个返回unit的无参“close”方法。这样可以安全地进行duck-typing混入。
abstract class A extends {def close:Unit}
只是一个抽象类,它的超类是Object。这只是Scala对无意义表达式的宽容语法。例如,您可以使用class X extends { def f = 1 }; new X().f
。 - Alexey还有一件事情没有提到:由于自类型不是所需类的层次结构的一部分,因此它们可以从模式匹配中排除,特别是当您在穷尽地匹配封闭层次结构时。当您想要建模正交行为时,这非常方便,例如:
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
马丁·奥德斯基原始的Scala论文可扩展组件抽象中的第2.3节“Selftype Annotations”实际上很好地解释了selftype的目的,不仅限于mixin组合:提供一种将类与抽象类型关联的替代方式。
论文中给出的示例如下,似乎没有优美的子类对应项:
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;
}
}
其他答案的简要概述:
你扩展的类型将对继承的类型公开,但self-types则不会
例如:class Cow { this: FourStomachs }
允许您使用仅适用于反刍动物(例如digestGrass
)的方法。 然而,扩展Cow的Trait将没有这样的特权。 另一方面,class Cow extends FourStomachs
将把digestGrass
暴露给任何extends Cow
的人。
self-types允许循环依赖关系,扩展其他类型则不允许
让我们从循环依赖开始。
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
虽然,如果你重写了自身类型的成员,你就会失去访问原始成员的权限,但仍可以通过继承使用super来访问。因此,相对于使用继承,真正获得的是:
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 Frame类,因此它可以用作自身类型,然后在实例化时混合使用。但是,必须在继承特质之前初始化val geomR
。因此,我们需要一个类来强制先初始化geomR
。可以通过多个正交特质继承类ScnVista
,这些特质本身也可以被继承。使用多个类型参数(泛型)提供了一种可替代的模块化形式。
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
接口的东西才能混入它。trait A { self:B => ... }
,那么声明X with A
只有在X扩展B时才有效。是的,您可以说X with A with Q
,其中Q不扩展B,但我相信kikibobo的观点是X受到如此限制。或者我错过了什么? - AmigoNico更新:一个主要的区别是自类型可以依赖于多个类(我承认这有点边角案例)。例如,你可以有
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
。
trait A[Self] {this: Self => }
是合法的,trait A[Self] extends Self
不合法。 - Blaisorblade