Traits Scala
Dans les précédents billets consacrés à Scala, nous avons traité du mécanisme d'inférence et des constructeurs et méthodes.
Dans ce billet, nous allons étudier les interfaces en Scala. Celles-ci ont peu de similarités avec les interfaces en Java. Le langage Java ne permet pas de faire de l'héritage multiple, mais les interfaces permettent en quelque sorte de contourner ce problème.
Exemple en Java
Nous allons prendre l'exemple d'une voiture hybride qui peut être soit à essence soit électrique. En Java nous aurions ceci :
public abstract String drive();
}
public interface IOilCar {
public void fill();
}
public interface IElectricalCar {
public void reload();
}
public class OilCar extends AbstractCar implements IOilCar {
public String drive() {
System.out.println("Drive an oil car");
return null;
}
public void fill() {
}
}
public class ElectricalCar extends AbstractCar implements IElectricalCar {
public String drive() {
System.out.println("Drive an electrical car");
return null;
}
public void reload() {
}
}
public class HybridCar extends AbstractCar implements IElectricalCar, IOilCar {
public String drive() {
return null;
}
public void reload() {
}
public void fill() {
}
}
Bien que la déclaration soit longue, nous voyons que la classe HybridCar est bien une OilCar et une ElectricalCar. Malheureusement les méthodes reload() et fill() sont implémentées plusieurs fois, ce qui implique de la redondance, de la verbosité et de potentielles erreurs de surcharge et/ou de logique. De plus si nous allons un peu plus loin, il est possible de créer des ElectricalCar qui ne sont pas des voitures... Certes d'un point de vue design cela ne se fait pas, mais rien ne nous l'interdit.
Scala a voulu corriger ce problème et propose une autre approche sous la forme des traits, qui sont en fait une combinaison des interfaces Java et des mixins Ruby.
Exemple en Scala
Ainsi, si nous reprenons notre exemple nous aurions :
def drive() = {
print("Drive an");
}
}
trait ElectricalCar extends Car {
override def drive = {
super.drive();
println(" electrical car");
}
def reload () = {
}
}
trait OilCar extends Car {
override def drive = {
super.drive();
println(" oil car");
}
def fill () = {
}
}
class hybridCar extends OilCar with ElectricalCar {
//...
}
@Override et override
En Java, il est fréquent de vouloir modifier le comportement d'une méthode dans les classes dérivées. L'annotation @Override a été introduite afin de vérifier que les méthodes d'une classe dérivée surchargent bien une méthode de la superclasse, mais celle-ci n'est pas obligatoire. En Scala, override est un mot clé du langage et si celui-ci est omis, une erreur de compilation sera générée. D'où la présence de override pour la méthode drive dans les classes dérivées.
Retour au code
Si nous faisons appel à la méthode drive d'une instance de hybridCar, le compilateur sait que celle-ci fait aussi référence à la méthode drive du trait ElectricalCar (on parle alors de mixin).
Ainsi avec le code suivant :
hCar.drive
Nous avons le résultat suivant :
electrical car
Bien entendu si nous changeons l'ordre du mixin, nous aurons :
oil car
Dans cet exemple en Scala, la classe hybridCar est bien un sous type de Car, ElectricalCar et OilCar. Les traits en Scala permettent non seulement de définir des méthodes/variables abstraites, mais aussi de définir des méthodes, et peuvent aussi étendre d'autres classes ou traits. La force des traits réside dans le fait que le compilateur va les traiter comme des classes héritées. Ainsi, les membres non abstraits des traits font en quelque sorte partie des sous-classes.
Nous pouvons dire que les traits Scala sont dans un certain sens un mix des notions d'interface et de classes abstraites Java. Néanmoins, le point noir les concernant est que les traits Scala n'acceptent pas de paramètres dans leurs constructeurs.
Les traits représentent un mécanisme puissant, car ils permettent de définir une interface que le développeur doit utiliser (ie. interface Java), mais aussi de définir des comportements (ie. abstract Java). L'exemple qui a été développé ici ne représente qu'une petite partie des possibilités qu'offrent les traits, et je vous invite à télécharger Scala et à essayer par vous-même les possibilités de ce langage fonctionnel.
Références
- The busy Java developer's guide to Scala: Of traits and behaviors
- The busy Java developer's guide to Scala: Implementation inheritance
- Scala for Java Refugees Part 5: Traits and Types









[...] Comme le code est destiné à une présentation, le test et le code sont dans le même fichier. Les traits sont une sorte d’hybride entre des interfaces Java et des mixins. Ils permettent de décrire le comportement que devra implémenter la classe mais aussi de fournir des implémentations de méthodes qui seront greffées dans le code de la classe qui implémente le trait. Il est ainsi possible d’hériter de comportement de plusieurs classes différentes. Vous pouvez en apprendre plus avec ces deux ressources en anglais et en français. [...]