Scala 可堆叠特性中的继承和代码重用

Scala 可堆叠特性中的继承和代码重用,scala,inheritance,mixins,traits,Scala,Inheritance,Mixins,Traits,在这个简化的实验中,我希望能够快速构建一个具有可堆叠特征的类,该类可以报告用于构建该类的特征。这让我强烈地想起了decorator模式,但我更喜欢在编译时而不是在运行时实现它 使用冗余代码的工作示例 class TraitTest { def report(d: Int) : Unit = { println(s"At depth $d, we've reached the end of our recursion") } } trait Moo extends TraitT

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

使用冗余代码的工作示例

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)
  }
}
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)。报告(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")
  }
}

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)
  }
}
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)。报告(0)
,我们现在看到:

> At depth 0, I make the sound 'Quack'
  At depth 1, we've reached the end of our recursion
问题1:表示“哞”的行到哪里去了

我猜Scala只看到一次
override def report(d:Int)
,因此只将它放在继承链中一次。我在抓救命稻草,但如果是这样的话,我怎么能解决这个问题呢

问题2:每个具体特征如何提供独特的
声音

在解决了第一个问题之后,我将假设执行
(带有嘎嘎的Moo的新traitest)的结果。由于
声音的继承将如何工作,报告(0)
将如下所示

> 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  

我们如何使每个特征都使用其实现中指定的
声音?

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

当构造一个具体的类时,所有继承的特征都会线性化,这样你就可以定义堆叠特征的顺序。如果您两次继承一个特征,那么只会包含第一次出现的特征。所以在

class C1 extends A with B 
class C2 extends C1 with X with B
线性化继承堆栈中B性状的位置将在A之后,但在C1和X之前。忽略第二个B mixin

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

class X extends A with T[Int] with T[String]
(这将在没有擦除的平台上工作,如.NET)

一些个人经验的建议

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)
  }
}
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"
}
我认为,虽然堆叠特征有时是一个不错的特性,但如果您有一个包含堆叠特征的大型继承层次结构,那么它可能会成为维护的噩梦。功能性取决于特征的混合顺序,因此只要简单地改变特征的顺序就可以破坏程序

此外,对不可变对象的类层次结构使用继承几乎需要使用显式的self-type参数,这会带来另一个复杂级别。例如,请参见scala集合中的xxxLike特征

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

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

  • 有一些工具可以强制执行特定的命令。例如,如果trait中的方法未标记为override,则不能将其混合到已实现该方法的类中。当然,如果你在一个特征中将一个方法标记为最终的,你就要确保它总是“在最上面”。在任何情况下,在特征中标记方法都是一个非常好的主意

  • 如果你决定使用复杂的特质层次结构,你需要一种方法来检查特质顺序。这以scala反射的形式存在。看看这个答案

  • 示例如何使用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控制台。

    这里是一个首选组合的示例。放大逻辑被重构

    我发现我必须每年使用一到两次抽象覆盖,否则脑细胞就会死亡

    在本例中,当您混入更多噪音时,动物会变得更吵

    它使用运行时反射,但您当然可以想象宏执行类似的操作。(你必须告诉它这是什么。)

    真正的代码当然会执行更有趣的转换;例如,在鸭子的叫声之后再加上一声猪的叫声,听起来就像是鹅在送鸡蛋

    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}"
    }
    
    软件包声音
    特征音{
    def声音:字符串
    }
    沉默延伸声音{
    def sound:String=“”
    }
    //鸭子总是更有趣
    鸭子沉默了
    目标放大器{
    导入reflect.runtime.currentMirror
    导入reflect.runtime.universe_
    
    def apply[A我做了一些修改,这些修改减少了代码重复,并通过强制用户将方法声明为
    抽象覆盖
    ,提醒用户调用
    super.report

      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)
    

    所以基本上问题1:由于线性化,
    覆盖def报告
    只显示一次,问题2或多或少是一个没有实际意义的问题。