La notion de Trait est inspirée des Mix-In du langage Ruby.
Un Trait peut être vu comme une interface permettant de définir des méthodes et
variables abstraites mais aussi des méthodes concrètes. Le corps d’un trait
peut contenir tout ce qu'une classe abstraite peut contenir, comme des
attributs et des méthodes :
trait Danse { var nature:String val nom:String def danser:Unit } trait Chante { def chanter = println("Chantons !") }
Une classe peut « hériter » (on dit plutôt mix in), de
plusieurs traits mais ne peut hériter que d’une seule classe. Chaque trait représente un service ou un
comportement encapsulé qu'il est possible de greffer dynamiquement ou
statiquement aux classes héritières.
Les traits peuvent être mixés avec des objets, c’est-à-dire qu’il
n’est pas nécessaire qu’une classe hérite d’un trait pour qu’une instance de
cette classe puisse recevoir le comportement de ce trait. Par exemple, on peut créer un objet quelconque
et injecter le comportement du trait Chanter :
val monChanteur = new Object with Chante monChanteur.chanter //affiche : Chantons !
Lorsqu’une classe étend/mixe plusieurs trait, on utilise le mot clé extends pour le premier et le mot clé with pour les autres :
class Rap extends Chante with Danse {
var nature = "RAP"
val nom = "rappeur"
override def danser = println("YO YO !")
}
Les traits sont traduits en bytecode par une interface, et le
code du trait est ajouté à la classe :
L’exemple suivant nous montre le bytecode décompilé de la
classe Rap mixée avec les trait Danse et Chante :
//Danse interface
public abstract interface Danse
{
public abstract String nature();
public abstract void nature_$eq(String paramString);
public abstract String nom();
public abstract void danser();
}
//Chante interface
public abstract interface Chante
{
public abstract void chanter();
}
//Rap class
public class Rap
implements Chante, Monde.Danse
{
private String nature;
private final String nom;
public void chanter()
{
Chante.class.chanter(this);
}
public void nature_$eq(String x$1)
{
this.nature = x$1;
}
public String nature()
{
return this.nature;
}
public Rap()
{
Chante.class.$init$(this);
this.nature = "RAP";
this.nom = "rappeur";
}
public String nom()
{
return this.nom;
}
public void danser()
{
Predef..MODULE$.println("YO YO !");
}
}
Contrairement à Java 8 avec le nouveau concept interface et
default méthode, un trait peut implémenter une méthode abstraite d’un autre
trait :
trait Bar {
def bar:Unit
}
trait Foo extends Bar{
def bar = println("implements bar method")
}
class FooBar extends Foo with Bar
def main(args: Array[String]) {
(new FooBar).bar //affiche : implements bar method
}
Avec Java 8, une erreur de conflit est générée :
interface Bar {
void bar();
}
interface Foo {
default void bar() {…}
}
class FooBar implements Foo, Bar {
// the method bar is considered in conflict and needs to resolved
}
En cas d'un réel conflit (une même méthode avec la même signature définit dans plusieurs traits mixés par une classe), il est possible d'effectuer le choix de la méthode à appeler grâce au mot clé : super[T].method . L'exemple suivant nous montre une classe qui implémente deux traits : Foo et Bar contenant l'implémentation de la méthode bar :
Pour finir, notez que l’ordre de l’apparence d’un trait dans le mixage (héritage) peut avoir un impact lors de l’exécution. La classe concrète est exécutée en dernier …
trait Bar {
def bar = println("Bar implements bar method")
}
trait Foo {
def bar = println("Foo implements bar method")
}
class FooBar extends Foo with Bar{
def fooBar = super[Foo].bar
def barBar = super[Bar].bar
override def bar = super[Bar].bar
}
def main(args: Array[String]) {
(new FooBar).bar //Bar implements bar method
}
Pour finir, notez que l’ordre de l’apparence d’un trait dans le mixage (héritage) peut avoir un impact lors de l’exécution. La classe concrète est exécutée en dernier …
trait T1 {
println("Trait 1 mixin")
def m1 = println("in method m1 of T1")
}
trait T2 {
println("Trait 2 mixin")
}
class B {
println("Class B constructor")
}
class A(p:String) extends B with T1 with T2{
println("Class A constructor")
}
//Main
/**
* Created by mtoure on 18/04/15.
*/
object Main {
def main(args: Array[String]) {
val a = new A("A")
}
}
//Affiche :
Class B constructor
Trait 1 mixin
Trait 2 mixin
Class A constructor
Process finished with exit code 0
Aucun commentaire:
Enregistrer un commentaire