Ricerca…


Sintassi

  • tratto ATIGHT {...}
  • classe AClass (...) estende ATrait {...}
  • classe AClass estende BClass con ATrait
  • classe AClass estende ATrait con BTrait
  • classe AClass estende ATrait con BTrait con CTrait
  • classe ATrait estende BTrait

Modifica impilabile con tratti

È possibile utilizzare i tratti per modificare i metodi di una classe, utilizzando i tratti in modo impilabile.

Il seguente esempio mostra come i tratti possono essere impilati. L'ordine dei tratti è importante. Usando un diverso ordine di tratti, si ottiene un comportamento diverso.

class Ball {
  def roll(ball : String) = println("Rolling : " + ball)
}

trait Red extends Ball {
  override def roll(ball : String) = super.roll("Red-" + ball)
}

trait Green extends Ball {
  override def roll(ball : String) = super.roll("Green-" + ball)
}

trait Shiny extends Ball {
  override def roll(ball : String) = super.roll("Shiny-" + ball)
}

object Balls {
  def main(args: Array[String]) {
    val ball1 = new Ball with Shiny with Red
    ball1.roll("Ball-1") // Rolling : Shiny-Red-Ball-1

    val ball2 = new Ball with Green with Shiny
    ball2.roll("Ball-2") // Rolling : Green-Shiny-Ball-2
  }
}

Nota che super è usato per invocare roll() metodo roll() in entrambi i tratti. Solo in questo modo possiamo ottenere modifiche impilabili. In caso di modifica impilabile, l'ordine di invocazione del metodo è determinato dalla regola di linearizzazione .

Nozioni di base sui tratti

Questa è la versione più basilare di un tratto in Scala.

trait Identifiable {
  def getIdentifier: String
  def printIndentification(): Unit = println(getIdentifier)
}

case class Puppy(id: String, name: String) extends Identifiable {
  def getIdentifier: String = s"$name has id $id"
}

Dal momento che nessuna classe super è dichiarata per tratto Identifiable , per impostazione predefinita si estende dalla classe AnyRef . Poiché in Identifiable viene fornita alcuna definizione per getIdentifier , la classe Puppy deve implementarla. Tuttavia, Puppy eredita l'implementazione di printIdentification da Identifiable .

Nella REPL:

val p = new Puppy("K9", "Rex")
p.getIdentifier  // res0: String = Rex has id K9
p.printIndentification()  // Rex has id K9

Risolvere il problema del diamante

Il problema dei diamanti , o ereditarietà multipla, è gestito da Scala usando i Tratti, che sono simili alle interfacce Java. I tratti sono più flessibili delle interfacce e possono includere metodi implementati. Questo rende tratti simili ai mixin in altre lingue.

Scala non supporta l'ereditarietà di più classi, ma un utente può estendere più tratti in una singola classe:

trait traitA {
  def name = println("This is the 'grandparent' trait.")
}

trait traitB extends traitA {
  override def name = {
    println("B is a child of A.")
    super.name
  }

}

trait traitC extends traitA {
  override def name = {
    println("C is a child of A.")
    super.name
  }
}

object grandChild extends traitB with traitC

grandChild.name

Qui grandChild eredita sia da traitB che da traitC , che a loro volta ereditano entrambi da traitA . L'output (sotto) mostra anche l'ordine di precedenza quando si risolvono quali implementazioni del metodo vengono chiamate per prime:

C is a child of A. 
B is a child of A. 
This is the 'grandparent' trait.

Si noti che, quando viene utilizzato super per invocare metodi in class o trait , la regola di linearizzazione entra in gioco per decidere la gerarchia delle chiamate. L'ordine di linearizzazione per grandChild sarà:

grandChild -> traitC -> traitB -> traitA -> AnyRef -> Any


Di seguito è un altro esempio:

trait Printer {
  def print(msg : String) = println (msg)
}

trait DelimitWithHyphen extends Printer {
  override def print(msg : String) {
    println("-------------")
    super.print(msg)
  }
}

trait DelimitWithStar extends Printer  {
  override def print(msg : String) {
    println("*************")
    super.print(msg)
  }
}

class CustomPrinter extends Printer with DelimitWithHyphen with DelimitWithStar

object TestPrinter{
  def main(args: Array[String]) {
    new CustomPrinter().print("Hello World!")
  }
}

Questo programma stampa:

*************
-------------
Hello World!

La linearizzazione per CustomPrinter sarà:

CustomPrinter -> DelimitWithStar -> DelimitWithHyphen -> Printer -> AnyRef -> Any

linearizzazione

In caso di modifica impilabile , Scala organizza le classi e i tratti in un ordine lineare per determinare la gerarchia delle chiamate al metodo, che è nota come linearizzazione . La regola di linearizzazione viene utilizzata solo per quei metodi che prevedono l'invocazione del metodo tramite super() . Consideriamo questo con un esempio:

class Shape {
  def paint (shape: String): Unit = {
    println(shape)
  }
}

trait Color extends Shape {
  abstract override def paint (shape : String) {
    super.paint(shape + "Color ")
  }
}

trait Blue extends Color {
  abstract override def paint (shape : String) {
    super.paint(shape + "with Blue ")
  }
}

trait Border extends Shape {
  abstract override def paint (shape : String) {
    super.paint(shape + "Border ")
  }
}

trait Dotted extends Border {
  abstract override def paint (shape : String) {
    super.paint(shape + "with Dotted ")
  }
}

class MyShape extends Shape with Dotted with Blue {
  override def paint (shape : String) {
    super.paint(shape)
  }
}

La linearizzazione avviene da dietro in avanti . In questo caso,

  1. La prima Shape sarà linearizzata, che assomiglia a:

    Shape -> AnyRef -> Any

  2. Quindi Dotted è linearizzato:

    Dotted -> Border -> Shape -> AnyRef -> Any

  3. Il prossimo in linea è il Blue . La linearizzazione di Normally Blue sarà:

    Blue -> Color -> Shape -> AnyRef -> Any

    perché, nella linearizzazione di MyShape fino ad ora ( Passo 2 ), Shape -> AnyRef -> Any è già apparso. Quindi, è ignorato. Pertanto, la linearizzazione Blue sarà:

    Blue -> Color -> Dotted -> Border -> Shape -> AnyRef -> Any

  4. Infine, verrà aggiunto Circle e l'ordine di linearizzazione finale sarà:

    Cerchio -> Blu -> Colore -> Punteggiato -> Bordo -> Forma -> AnyRef -> Qualsiasi

Questo ordine di linearizzazione decide l'ordine di invocazione dei metodi quando super è usato in qualsiasi classe o tratto. Viene invocata la prima implementazione del metodo da destra, nell'ordine di linearizzazione. Se viene new MyShape().paint("Circle ") , verrà stampato:

Circle with Blue Color with Dotted Border 

Maggiori informazioni sulla linearizzazione possono essere trovate qui .



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow