dimanche 26 avril 2015

Scala & Trait : un concept à clarifier !

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 :


 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