Il y a 15 ans -
Temps de lecture 4 minutes
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 class AbstractCar { 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 :
abstract class Car { 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 :
val hCar = new hybridCar(); hCar.drive
Nous avons le résultat suivant :
Drive an oil car electrical car
Bien entendu si nous changeons l’ordre du mixin, nous aurons :
Drive an electrical car 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.
Commentaire