Il y a une section dans Programming in Scala appelée "Traiter, ou ne pas traiter ?" qui aborde cette question. Comme la première édition est disponible en ligne, j'espère que je peux la citer intégralement ici. (Tout programmeur Scala sérieux devrait acheter le livre) :
Chaque fois que vous implémentez une collection réutilisable de comportements, vous allez vous devrez décider si vous voulez utiliser un trait ou une classe abstraite. Il n'y a pas de règle absolue, mais cette section contient quelques lignes directrices à à prendre en compte.
Si le comportement ne sera pas réutilisé alors faites-en une classe concrète. C'est n'est pas un comportement réutilisable après tout.
S'il est susceptible d'être réutilisé dans plusieurs classes sans lien entre elles. pour en faire un trait. Seuls les traits peuvent être mélangés dans différentes parties de la hiérarchie des classes.
Si vous voulez en hériter dans le code Java utiliser une classe abstraite. Étant donné que les traits avec code n'ont pas d'analogie proche avec Java, il tend à être maladroite d'hériter d'un trait dans une classe Java. Hériter d'une Scala, quant à elle, est exactement comme l'héritage d'une classe Java. À l'exception d'un trait Scala ne comportant que des membres abstraits qui se traduit directement par une interface Java. directement à une interface Java. Vous pouvez donc définir de tels traits même si vous n'hésitez donc pas à définir de tels traits, même si vous vous attendez à ce que le code Java en hérite. Voir le chapitre 29 pour plus d'informations sur le travail en commun avec Java et Scala.
Si vous envisagez de le distribuer sous forme compilée et vous attendez des groupes extérieurs que des groupes extérieurs écrivent des classes qui en héritent, vous pourriez pencher vers l'utilisation d'une classe abstraite. Le problème est que lorsqu'un trait gagne ou perd un membre. membre, les classes qui en héritent doivent être recompilées, même si elles n'ont pas changé. elles n'ont pas changé. Si les clients extérieurs ne font appel qu'au comportement au lieu d'en hériter, l'utilisation d'un trait convient.
Si l'efficacité est très importante je penche pour l'utilisation d'une classe. La plupart des moteurs d'exécution Java font que l'invocation d'une méthode virtuelle d'un membre de classe est plus rapide que l'invocation d'une méthode d'interface. Les traits sont compilés en interfaces et peuvent donc avoir un léger surcoût de performance. Toutefois, vous ne devez faire ce choix que si vous savez que le trait en question constitue un goulot d'étranglement en termes de performances et que vous avez la preuve que l'utilisation d'une classe à la place résout réellement le problème.
Si vous ne savez toujours pas après avoir considéré ce qui précède, commencez par le faire en tant que trait. Vous pouvez toujours le modifier plus tard, et en général l'utilisation d'un trait permet de garder plus d'options ouvertes.
Comme l'a mentionné @Mushtaq Ahmed, un trait ne peut pas avoir de paramètres passés au constructeur primaire d'une classe.
Une autre différence concerne le traitement des super
.
L'autre différence entre les classes et les traits est qu'alors que dans les classes, super
sont liés statiquement, dans les traits, ils sont liés dynamiquement. Si vous écrivez super.toString
dans une classe, vous savez exactement quelle implémentation de méthode sera invoquée. Cependant, lorsque vous écrivez la même chose dans un trait, l'implémentation de la méthode à invoquer pour le super appel n'est pas définie lorsque vous définissez le trait.
Voir le reste de Chapitre 12 pour plus de détails.
Edit 1 (2013) :
Il y a une différence subtile dans la façon dont les classes abstraites se comportent par rapport aux traits. L'une des règles de linéarisation consiste à préserver la hiérarchie d'héritage des classes, ce qui tend à pousser les classes abstraites plus loin dans la chaîne, alors que les traits peuvent volontiers y être mélangés. Dans certaines circonstances, il est en fait préférable d'être en dernière position de la linéarisation des classes, et les classes abstraites peuvent donc être utilisées à cette fin. Voir contraindre la linéarisation des classes (ordre des mixins) en Scala .
Edit 2 (2018) :
Depuis la version 2.12 de Scala, le comportement de la compatibilité binaire de trait a changé. Avant la version 2.12, l'ajout ou la suppression d'un membre du trait nécessitait une recompilation de toutes les classes qui héritent du trait, même si les classes n'ont pas changé. Cela est dû à la façon dont les traits étaient encodés dans la JVM.
Depuis Scala 2.12, les traits compiler vers des interfaces Java L'exigence s'est donc un peu relâchée. Si le trait fait l'une des choses suivantes, ses sous-classes doivent toujours être recompilées :
- définir des champs (
val
o var
mais une constante est acceptable. final val
sans type de résultat)
- en appelant
super
- déclarations d'initialisation dans le corps
- l'extension d'une classe
- en s'appuyant sur la linéarisation pour trouver des implémentations dans le bon supertrait
Mais si ce n'est pas le cas, vous pouvez désormais mettre à jour le trait sans rompre la compatibilité binaire.