moa*_*tra 5 inheritance scala traits mixins
在这个简化的实验中,我希望能够快速构建一个具有可堆叠特征的类,该类可以报告用于构建它的特征.这让我想起了装饰器模式,但我更喜欢在编译时而不是在运行时实现它.
冗余代码的工作示例
class TraitTest {
def report(d: Int) : Unit = {
println(s"At depth $d, we've reached the end of our recursion")
}
}
trait Moo extends TraitTest {
private def sound = "Moo"
override def report(d: Int) : Unit = {
println(s"At depth $d, I make the sound '$sound'")
super.report(d+1)
}
}
trait Quack extends TraitTest {
private def sound = "Quack"
override def report(d: Int) : Unit = {
println(s"At depth $d, I make the sound '$sound'")
super.report(d+1)
}
}
Run Code Online (Sandbox Code Playgroud)
(new TraitTest with Moo with Quack).report(0)然后执行将报告:
> At depth 0, I make the sound 'Quack'
At depth 1, I make the sound 'Moo'
At depth 2, we've reached the end of our recursion
Run Code Online (Sandbox Code Playgroud)
不幸的是,那里有很多冗余的代码让我的眼睛抽搐.我试图清理它会让我:
没有冗余代码的非工作示例
class TraitTest {
def report(d: Int) : Unit = {
println(s"At depth $d, we've reached the end of our recursion")
}
}
abstract trait Reporter extends TraitTest {
def sound : String
override def report(d: Int) : Unit = {
println(s"At depth $d, I make the sound '${sound}'")
super.report(d+1)
}
}
trait Moo extends Reporter {
override def sound = "Moo"
}
trait Quack extends Reporter{
override def sound = "Quack"
}
Run Code Online (Sandbox Code Playgroud)
当我们再次执行时(new TraitTest with Moo with Quack).report(0),我们现在看到:
> At depth 0, I make the sound 'Quack'
At depth 1, we've reached the end of our recursion
Run Code Online (Sandbox Code Playgroud)
问题1: 'Moo'的路线在哪里?
我猜Scala只看到override def report(d: Int)一次,因此只将它放在继承链中一次.我正在抓住稻草,但如果是这样的话,我该如何解决这个问题呢?
问题2:每个具体特征如何提供独特的特征sound?
在解决了第一个问题之后,我会假设执行的结果(new TraitTest with Moo with Quack).report(0)看起来像下面这样,因为继承sound将如何工作.
> At depth 0, I make the sound 'Quack'
At depth 1, I make the sound 'Quack'
At depth 2, we've reached the end of our recursion
Run Code Online (Sandbox Code Playgroud)
我们如何才能使每个特征使用sound其实现中指定的特征?
特征最多可以遗传一次.它基本上只是一个由scala编译器使用非抽象方法扩展的java接口.
构造具体类时,所有继承的特征都会线性化,因此您可以定义堆叠特征的顺序.如果继承两次特征,则只包含第一个特征.所以
class C1 extends A with B
class C2 extends C1 with X with B
Run Code Online (Sandbox Code Playgroud)
线性化继承堆栈中B特征的位置将在A之后但在C1和X之前.第二个B mixin被忽略.
即使使用类型参数这样的技巧也不会因擦除而起作用.所以这不起作用:
class X extends A with T[Int] with T[String]
Run Code Online (Sandbox Code Playgroud)
(这适用于没有擦除的平台,如.NET)
个人经验的一些建议
我认为堆叠特征有时是一个很好的特性,如果你有一个具有堆叠特征的大型继承层次结构,它可能是一个维护噩梦.功能取决于特征混合的顺序,因此只需对特征顺序进行简单更改即可破坏程序.
此外,对不可变对象的类层次结构使用继承几乎需要使用显式自我类型类型参数,这会带来另一层复杂性.例如,请参阅scala集合中的xxxLike特征.
当特征不重叠时,特征当然非常有用且没有问题.但总的来说,规则有利于组合而不是继承对于scala和其他OO语言一样正确.Scala为您提供了强大的特性继承工具,但它也为您提供了更强大的组合工具(值类,隐含,类型类模式......)
帮助管理大型特质层次结构
有一些工具可以强制执行某个订单.例如,如果特征中的方法未标记为覆盖,则不能将其混合到已实现该方法的类中.当然,如果您将某个方法标记为特征中的最终方法,则可以确保它始终处于"最顶层".在任何情况下,在特征中标记方法最终是一个非常好的主意.
如果您决定使用复杂的特征层次结构,则需要一种方法来检查特征顺序.这以scala反射的形式存在.使用反射查看此答案mixin order.
示例如何使用scala反射获取特征顺序
import scala.reflect.runtime.universe._
class T extends TraitTest with Moo with Quack
scala> typeOf[T].baseClasses
res4: List[reflect.runtime.universe.Symbol] =
List(class T, trait Quack, trait Moo, class TraitTest, class Object, class Any)
Run Code Online (Sandbox Code Playgroud)
您需要在类路径中包含scala-reflect.jar,现在它是一个单独的依赖项.我刚刚使用了一个sbt项目
libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.10.2"
Run Code Online (Sandbox Code Playgroud)
build.sbt并启动sbt控制台.