Réponses:
Je peux penser à deux différences
Il y a une section dans Programmation dans Scala appelée "Pour trait, ou pas pour trait?" qui répond à cette question. Puisque la 1ère édition est disponible en ligne, j'espère que c'est bien de citer le tout ici. (Tout programmeur Scala sérieux devrait acheter le livre):
Chaque fois que vous implémentez une collection de comportements réutilisable, vous devrez décider si vous souhaitez utiliser un trait ou une classe abstraite. Il n'y a pas de règle ferme, mais cette section contient quelques directives à considérer.
Si le comportement ne sera pas réutilisé , faites-en une classe concrète. Ce n'est pas un comportement réutilisable après tout.
S'il peut être réutilisé dans plusieurs classes indépendantes , faites-en un trait. Seuls les traits peuvent être mélangés dans différentes parties de la hiérarchie des classes.
Si vous souhaitez en hériter en code Java , utilisez une classe abstraite. Étant donné que les traits avec du code n'ont pas d'analogue Java proche, il a tendance à être gênant d'hériter d'un trait dans une classe Java. L'héritage d'une classe Scala, quant à lui, est exactement comme l'héritage d'une classe Java. À titre d'exception, un trait Scala avec uniquement des membres abstraits se traduit directement par une interface Java, vous devriez donc vous sentir libre de 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 l'utilisation conjointe de Java et Scala.
Si vous prévoyez de le distribuer sous forme compilée et que vous vous attendez à ce que des groupes externes écrivent des classes qui en héritent, vous pourriez vous pencher vers l'utilisation d'une classe abstraite. Le problème est que lorsqu'un trait gagne ou perd un membre, toutes les classes qui en héritent doivent être recompilées, même si elles n'ont pas changé. Si des clients externes n'appellent que le comportement, au lieu d'en hériter, alors utiliser un trait est très bien.
Si l'efficacité est très importante , penchez-vous vers l'utilisation d'une classe. La plupart des runtimes Java font une invocation de méthode virtuelle d'un membre de classe une opération plus rapide qu'une invocation de méthode d'interface. Les traits sont compilés sur les interfaces et peuvent donc payer une légère surcharge de performances. Cependant, vous ne devez faire ce choix que si vous savez que le trait en question constitue un goulot d'étranglement des 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 en faire un trait. Vous pouvez toujours le modifier plus tard, et en général, l'utilisation d'un trait laisse plus d'options ouvertes.
Comme l'a mentionné @Mushtaq Ahmed, un trait ne peut avoir aucun paramètre transmis au constructeur principal d'une classe.
Une autre différence est le traitement des super
.
L'autre différence entre les classes et les traits est que, alors que dans les classes, les
super
appels sont liés statiquement, dans les traits, ils sont liés dynamiquement. Si vous écrivezsuper.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 à appeler pour le super appel n'est pas définie lorsque vous définissez le trait.
Voir le reste du chapitre 12 pour plus de détails.
Édition 1 (2013):
Il existe une différence subtile dans le comportement des classes abstraites par rapport aux traits. L'une des règles de linéarisation est qu'elle préserve la hiérarchie d'héritage des classes, ce qui tend à pousser les classes abstraites plus tard dans la chaîne tandis que les traits peuvent être mélangés avec bonheur. Dans certaines circonstances, il est en fait préférable d'être dans la dernière position de la linéarisation de classe , donc des classes abstraites pourraient être utilisées pour cela. Voir contrainte de linéarisation de classe (ordre de mixage) dans Scala .
Édition 2 (2018):
Depuis Scala 2.12, le comportement de compatibilité binaire de trait a changé. Avant la 2.12, l'ajout ou la suppression d'un membre au trait nécessitait une recompilation de toutes les classes héritant du trait, même si les classes n'avaient pas changé. Cela est dû à la façon dont les traits ont été encodés dans JVM.
Depuis Scala 2.12, les traits se compilent vers les interfaces Java , donc l'exigence s'est un peu relâchée. Si le trait fait l'une des actions suivantes, ses sous-classes nécessitent toujours une recompilation:
- définir des champs (
val
ouvar
, mais une constante est correcte -final val
sans type de résultat)- appel
super
- instructions d'initialisation dans le corps
- extension d'une classe
- s'appuyant sur la linéarisation pour trouver des implémentations dans le supertrait de droite
Mais si le trait ne fonctionne pas, vous pouvez maintenant le mettre à jour sans rompre la compatibilité binaire.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- Quelqu'un pourrait-il expliquer quelle est la différence ici? extends
vs with
?
extends
et with
. C'est purement syntaxique. Si vous héritez de plusieurs modèles, le premier obtient extend
, tous les autres obtiennent with
, c'est tout. Pensez with
comme une virgule: class Foo extends Bar, Baz, Qux
.
Pour tout ce que cela vaut, la programmation d'Odersky et al dans Scala recommande que, en cas de doute, vous utilisez des traits. Vous pouvez toujours les changer en classes abstraites plus tard si nécessaire.
Outre le fait que vous ne pouvez pas étendre directement plusieurs classes abstraites, mais que vous pouvez mélanger plusieurs traits dans une classe, il convient de mentionner que les traits sont empilables, car les super-appels dans un trait sont liés dynamiquement (il s'agit d'une classe ou d'un trait mélangé avant l'actuel).
Extrait de la réponse de Thomas dans Différence entre classe abstraite et caractère :
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
Dans Programming Scala, les auteurs disent que les classes abstraites établissent une relation classique "est-un" orientée objet tandis que les traits sont une voie de composition scala.
Les classes abstraites peuvent contenir un comportement - Elles peuvent être paramétrées avec des arguments de constructeur (quels traits ne peuvent pas) et représenter une entité fonctionnelle. Les traits représentent plutôt une seule fonctionnalité, une interface d'une fonctionnalité.
trait Enumerable
avec beaucoup de fonctions d'assistance, je ne les appellerais pas comportement mais simplement des fonctionnalités liées à une seule fonctionnalité.