5

この単純化された実験では、ビルドに使用されたトレイトを報告できるスタック可能なトレイトを持つクラスをすばやくビルドできるようにしたいと考えています。これはデコレータ パターンを強く思い出させますが、これは実行時ではなくコンパイル時に実装することを好みます。

冗長コードを使用した作業例

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 は1 回しか認識override def report(d: Int)しないため、継承チェーンに 1 回だけ配置すると思います。私はストローをつかんでいますが、そうであれば、どうすればそれを回避できますか?

質問 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各特性がその実装で指定されたものを使用するようにするにはどうすればよいでしょうか?

4

3 に答える 3

7

特性は、最大 1 回継承できます。これは基本的に、scala コンパイラーによって非抽象メソッドで拡張された単なる Java インターフェースです。

具象クラスが構築されると、継承されたすべての特性が線形化されるため、スタックされた特性の順序が定義されます。特性を 2 回継承すると、最初に出現したものだけが含まれます。だから

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

線形化された継承スタック内の B トレイトの位置は、A の後、C1 と X の前になります。2 番目の B mixin は無視されます。

型パラメーターを使用するようなトリックでさえ、消去のために機能しません。したがって、これは機能しません:

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

(これは、.NET などの消去のないプラットフォームで機能します)

個人的な経験からのアドバイス

トレイトをスタックすることは良い機能である場合もありますが、トレイトをスタックした大規模な継承階層がある場合、メンテナンスの悪夢のようなものになる可能性があります。機能は、特性が混合される順序に依存するため、特性の順序を単純に変更するだけで、プログラムが壊れる可能性があります。

また、不変オブジェクトのクラス階層に継承を使用するには、明示的な自己型型パラメーターを使用する必要がほとんどあり、これにより別のレベルの複雑さがもたらされます。たとえば、scala コレクションの xxxLike トレイトを参照してください。

もちろん、特性が重複していない場合、特性は非常に便利で問題はありません。しかし、一般に、継承よりも合成を優先するルールは、他の OO 言語と同様に scala にも当てはまります。Scala は、トレイトを使用した継承のための強力なツールを提供しますが、合成のためのさらに強力なツール (値クラス、暗黙、型クラス パターンなど) も提供します。

大規模な特性階層の管理を支援

  1. 特定の順序を強制するためのツールがいくつかあります。たとえば、特性のメソッドがオーバーライドとマークされていない場合、そのメソッドを既に実装しているクラスに混合することはできません。そしてもちろん、メソッドをトレイトで final としてマークすると、それが常に「最上位」になることが保証されます。いずれにせよ、trait でメソッド final をマークすることは非常に良い考えです。

  2. 複雑なトレイト階層を使用することにした場合は、トレイトの順序を検査する方法が必要になります。これは、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"

build.sbt に移動し、sbt consoleを起動しました。

于 2013-07-06T07:09:25.877 に答える
3

これは構成を優先する例です。増幅ロジックがリファクタリングされます。

abstract override年に 1、2 回使わないと脳細胞が死んでしまうことがわかりました。

この例では、より多くのノイズを混合すると、動物はよりうるさくなります。

これはランタイム リフレクションを使用しますが、もちろん、同様のことを行うマクロを想像することもできます。(何が何であるかを伝える必要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}"
}
于 2013-07-06T22:46:26.990 に答える