可堆叠特征中的继承和代码重用 [英] Inheritance and code reuse in stackable traits

查看:115
本文介绍了可堆叠特征中的继承和代码重用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在这个简化的实验中,我希望能够快速构建一个具有可堆叠特征的类,该类可以报告用于构建它的特征。这让我想起装饰器模式,但我更喜欢在编译时而不是在运行时实现它。

In this simplified experiment, I want to be able to quickly build a class with stackable traits that can report on what traits were used to build it. This reminds me strongly of the decorator pattern, but I'd prefer to have this implemented at compile time rather than at runtime.

冗余代码的工作示例

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)
  }
}

执行(带有Quack的Moo新TraitTest).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 

不幸的是,那里有很多冗余代码让我的眼睛抽搐。我尝试清理它时会让我:

Unfortunately, there's a lot of redundant code in there that makes my eye twitch. My attempt at cleaning it up leads me to:

没有冗余代码的非工作示例

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"
}

当我们再次执行时(带有Moo的新TraitTest)嘎嘎).report(0),我们现在看到:

When we once again execute (new TraitTest with Moo with Quack).report(0), we now see:

> At depth 0, I make the sound 'Quack'
  At depth 1, we've reached the end of our recursion

问题1:'Moo'的行在哪里?

Question 1: Where did the line for 'Moo' go?

我猜Scala只看到了覆盖def报告(d:Int)一次,因此只将它放在继承链中一次。我正在抓住稻草,但如果是这样的话,我该如何解决这个问题呢?

I'm guessing that Scala only sees override def report(d: Int) the one time, and therefore only puts it in the inheritance chain once. I'm grasping at straws, but if that's the case, how can I work around that?

问题2:每个具体特征怎么样?提供一个独特的声音

Question 2: How can each concrete trait supply a unique sound?

解决第一个问题后,我会假设执行<$ c $的结果c>(带有Quack的Moo的新TraitTest).report(0)看起来类似于以下内容,因为 sound 的继承将如何工作。

After solving the first question, I would assume the results of executing (new TraitTest with Moo with Quack).report(0) would look something like the following, due to how the inheritance of sound would work.

> 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  

我们如何才能使每个特性使用其中指定的声音实现?

How can we make it so that each trait uses the sound specified in it's implementation?

推荐答案

特征最多可以继承一次。它基本上只是一个由scala编译器用
非抽象方法扩展的java接口。

A trait can be inherited at most once. It is basically just a java interface extended with non-abstract methods by the scala compiler.

构建具体类时,所有继承的特征都会线性化,因此您可以定义堆积特征的顺序。如果您继承了两次特征,则只会包含第一个特征。所以在

When a concrete class is being constructed, all inherited traits get linearized so you have a defined order of your stacked traits. If you inherit a trait twice, just the first occurrence will be included. So in

class C1 extends A with B 
class C2 extends C1 with X with B

线性化继承堆栈中B特征的位置将在A之后但在C1和X之前。第二个B mixin被忽略。

The position of the B trait in the linearized inheritance stack will be after A but before C1 and X. The second B mixin is ignored.

即使使用类型参数这样的技巧也不会因擦除而起作用。所以这不起作用:

Even tricks like using type parameters will not work due to erasure. So this will not work:

class X extends A with T[Int] with T[String]

(这适用于没有擦除的平台,如.NET)

(This would work on platforms without erasure such as .NET)

来自个人经验的一些建议

我认为堆叠特征有时是一个很好的功能,如果你有一个具有堆叠特征的大型继承层次结构可能是一个维护噩梦。功能取决于特征混合的顺序,因此只需对特征顺序进行简单更改即可破坏程序。

I think while stacking traits is sometimes a nice feature, if you have a large inheritance hierarchy with stacked traits it can be something of a maintenance nightmare. Functionality depends on the order in which traits are being mixed in, so just a simple change in the order of traits can break your program.

此外,对不可变对象的类层次结构使用继承几乎需要使用显式自我类型类型参数,这会带来另一层复杂性。例如,请参阅scala集合中的xxxLike特征。

Also, using inheritance for class hierarchies of immutable objects pretty much requires the use of an explicit self-type type parameter, which brings its another level of complexity. See the xxxLike traits in the scala collections for example.

当特征非重叠时,特征当然非常有用且没有问题。但总的来说,规则支持组合优于继承对于scala和其他OO语言一样正确。 Scala为您提供了强大的特性继承工具,但它也为您提供了更强大的组合工具(值类,隐含,类型类型,......)

Traits are of course very useful and unproblematic when they are non-overlapping. But in general, the rule favor composition over inheritance is just as true for scala as for other OO languages. Scala gives you powerful tools for inheritance with traits, but it also gives you arguably even more powerful tools for composition (value classes, implicits, the typeclass pattern, ...)

帮助管理大型特质层次结构


  1. 有一些工具可以强制执行某个订单。例如,如果特征中的方法未标记为覆盖,则不能将其混合到已实现该方法的类中。当然,如果您将某个方法标记为特征中的最终方法,则可以确保它始终处于最顶层。在任何情况下,在特征中标记方法最终是一个非常好的主意。

  1. There are some tools to enforce a certain order. For example if a method in a trait is not marked override, you can not mix it into a class that already implements the method. And of course if you mark a method as final in a trait, you ensure that it is always "on top". Marking methods final in traits is a very good idea in any case.

如果您决定使用复杂的特征层次结构,则需要一种方法来检查特征顺序。这以scala反射的形式存在。请参阅此答案使用反射混合订单

If you decide to go with a complex trait hierarchy, you will need a way to inspect the trait order. This exists in the form of scala reflection. See this answer mixin order using reflection.

如何使用scala反射获取特征订单的示例

Example how to get trait order using scala reflection

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)

您需要在类路径中包含scala-reflect.jar,现在它是一个单独的依赖项。我刚刚使用了一个sbt项目,添加了

You will need to include scala-reflect.jar on the classpath though, which is now a separate dependency. I just used a sbt project, added

libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.10.2"

到build.sbt并启动 sbt console

to build.sbt and started sbt console .

这篇关于可堆叠特征中的继承和代码重用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆