【问题标题】:Inheritance and code reuse in stackable traits可堆叠特征中的继承和代码重用
【发布时间】:2013-07-06 06:27:09
【问题描述】:

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

使用冗余代码的工作示例

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

执行(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 

不幸的是,里面有很多冗余代码让我眼花缭乱。我试图清理它导致我:

没有冗余代码的无效示例

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

当我们再次执行(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

问题 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  

我们怎样才能让每个特征都使用在其实现中指定的sound

【问题讨论】:

    标签: scala inheritance mixins traits


    【解决方案1】:

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

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

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

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

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

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

    (这可以在没有擦除的平台上工作,例如 .NET)

    个人经验的一些建议

    我认为虽然堆叠特征有时是一个不错的功能,但如果您有一个带有堆叠特征的大型继承层次结构,那么它可能是维护的噩梦。功能取决于混入特征的顺序,因此只需更改特征顺序即可破坏您的程序。

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

    当特征不重叠时,它们当然非常有用且没有问题。但总的来说,优先组合优于继承的规则对于 scala 和其他 OO 语言一样适用。 Scala 为您提供了强大的特征继承工具,但它也为您提供了可以说是更强大的组合工具(值类、隐式、类型类模式……)

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

    1. 有一些工具可以强制执行某个命令。例如,如果 trait 中的方法未标记为 override,则不能将其混入已经实现该方法的类中。当然,如果您在 trait 中将方法标记为最终方法,则可以确保它始终处于“顶部”。无论如何,在特征中标记最终方法是一个非常好的主意。

    2. 如果您决定使用复杂的特征层次结构,您将需要一种方法来检查特征顺序。这以 scala 反射的形式存在。看到这个答案mixin order using reflection

    示例如何使用 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)
    

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

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

    构建.sbt 并启动sbt 控制台

    【讨论】:

    • 所以基本上问题 1:由于线性化,override def report 只出现一次,问题 2 或多或少是一个有争议的问题。
    【解决方案2】:

    这里是一个优先组合的例子。放大逻辑被重构。

    我发现我必须每年使用一次或两次abstract override,否则脑细胞会死亡。

    在这个例子中,当你混合更多的噪音时,动物会变得更吵。

    它使用运行时反射,当然你可以想象一个宏做类似的事情。 (你必须告诉它this 是什么。)

    真正的代码当然会执行更有趣的转换;例如,在鸭子声之后混入猪声,听起来就像鹅在送鸡蛋。

    package sounds
    
    trait Sound {
      def sound: String
    }
    
    trait Silent extends Sound {
      def sound: String = ""
    }
    
    // duck is always funnier
    trait Duck extends Silent
    
    object Amplifier {
      import reflect.runtime.currentMirror
      import reflect.runtime.universe._
      def apply[A <: Sound : TypeTag](x: Any): Int = {
        val im = currentMirror reflect x
        val tpe = im.symbol.typeSignature
        var i = -1
        for (s <- tpe.baseClasses) {
          if (s.asClass.toType =:= typeOf[A]) i = 0
          else if (s.asClass.toType <:< typeOf[Noise]) i += 1
        }
        i
      }
    }
    
    trait Noise
    trait NoisyQuack extends Sound with Noise {
      abstract override def sound: String = super.sound + noise * amplification
      private val noise = "quack"
      private def amplification: Int = Amplifier[NoisyQuack](this)
    }
    trait NoisyGrunt extends Sound with Noise {
      abstract override def sound: String = super.sound + noise * amplification
      private val noise = "grunt"
      private def amplification: Int = Amplifier[NoisyGrunt](this)
    }
    
    object Test extends App {
      val griffin = new Duck with NoisyQuack with NoisyGrunt {
        override def toString = "Griffin"
      }
      Console println s"The $griffin goes ${griffin.sound}"
    }
    

    【讨论】:

      【解决方案3】:

      我进行了一些修改,以减少代码重复并提醒用户调用super.report,强制他将方法声明为abstract override

        trait TraitTest {
          def report(d: Int): Unit
      
          def reportSound(d: Int, sound: => String): Unit = {
            println(s"At depth $d, I make the sound '$sound'")
          }
        }
      
        trait TraitTestRoot extends 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"
      
          abstract override def report(d: Int): Unit = {
            reportSound(d, sound)
            super.report(d + 1)
          }
        }
      
        trait Quack extends TraitTest {
          private def sound = "Quack"
      
          abstract override def report(d: Int): Unit = {
            reportSound(d, sound)
            super.report(d + 1)
          }
        }
      
        (new TraitTestRoot with Moo with Quack).report(0)
      

      【讨论】:

        猜你喜欢
        • 2020-11-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-02
        • 1970-01-01
        • 2013-12-21
        相关资源
        最近更新 更多