Scala Language
Tratti
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,
La prima
Shape
sarà linearizzata, che assomiglia a:Shape -> AnyRef -> Any
Quindi
Dotted
è linearizzato:Dotted -> Border -> Shape -> AnyRef -> Any
Il prossimo in linea è il
Blue
. La linearizzazione di NormallyBlue
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 linearizzazioneBlue
sarà:Blue -> Color -> Dotted -> Border -> Shape -> AnyRef -> Any
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 .