Quel est l'avantage d'utiliser des classes abstraites au lieu de traits de caractère?
Quel est l'avantage d'utiliser une classe abstraite au lieu d'un trait (en dehors de la performance)? Il semble que les classes abstraites peuvent être remplacées par des traits dans la plupart des cas.
8 réponses
je peux penser à deux différences
- les classes Abstraites peuvent avoir des paramètres du constructeur ainsi que les paramètres de type. Les Traits ne peuvent avoir que des paramètres de type. Il a été discuté qu'à l'avenir, même les traits peuvent avoir des paramètres de constructeur Les classes
- Abstract sont totalement interopérables avec Java. Vous pouvez les appeler depuis le code Java sans aucun papier. Les Traits ne sont pleinement interopérables que s'ils ne contiennent aucune mise en œuvre. code
il y a une section dans la programmation À Scala appelée " to trait, or not to trait?" qui répond à cette question. Puisque le 1er ed est disponible en ligne, j'espère que c'est ok de citer toute la chose ici. (Tout programmeur Scala sérieux devrait acheter le livre):
chaque fois que vous mettez en œuvre une collection réutilisable de comportement, vous devez décider si vous souhaitez utiliser un trait ou d'une classe abstraite. Il n'y a aucune règle, mais cette section contient quelques lignes directrices pour considérer.
si le comportement ne sera pas réutilisé , alors en faire une classe de béton. Il n'est pas réutilisable comportement, après tout.
si elle peut être réutilisée dans plusieurs classes non apparentées , en faire un trait. Seuls les traits peuvent être mélangés dans différentes parties de la hiérarchie de classe.
Si vous voulez hériter de lui dans le code Java , utilisez une classe abstraite. Puisque les traits avec le code n'ont pas un analogue de Java proche, il tend à être maladroit d'hériter d'un trait dans une classe Java. Héritage d'une La classe Scala, quant à elle, est exactement comme hériter d'une classe Java. À une exception près, un trait de Scala avec seulement des membres abstraits traduit directement à une interface Java, donc vous devriez vous sentir libre de définir un tel même si vous vous attendez à ce que le code Java en hérite. Voir Chapitre 29 pour plus d'informations sur le travail avec Java et Scala ensemble.
si vous prévoyez de le distribuer sous forme compilée , et vous attendez à l'extérieur groupes pour écrire des classes héritant de lui, vous pourriez pencher vers utiliser une classe abstraite. Le problème est que lorsque un trait gagne ou perd un membre, toutes les classes qui héritent de lui doit être recompilé, même si ils n'ont pas changé. Si les clients extérieurs ne feront appel à la comportement, au lieu de hériter de lui, puis 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 Java runtimes faire une méthode virtuelle invocation d'un membre de classe plus rapide opération que l'invocation d'une méthode d'interface. Les Traits sont compilés pour les interfaces et peut donc payer un léger frais généraux de performance. Cependant, vous devez faire ce choix que si vous savez que le trait en cause constitue une prestation goulot d'étranglement et preuves que l'utilisation d'une classe à la place résout en fait le problème.
si vous ne savez toujours pas , après avoir considéré ce qui précède, puis commencer par faire un trait de caractère. Vous pouvez toujours le changer plus tard, et en général l'utilisation d'un trait permet de garder plus d'options ouvertes.
comme @Mushtaq Ahmed l'a mentionné, un trait ne peut avoir aucun paramètre passé au constructeur principal d'une classe.
une Autre différence est le traitement de super
.
l'autre différence entre les classes et les traits est que tandis que dans les classes,
super
les appels sont statiquement liés, dans les traits, ils sont dynamiquement liés. Si vous écrivezsuper.toString
dans une classe, vous savez exactement quelle implémentation de méthode sera invoquée. Quand vous écrivez la même chose dans un trait, cependant, l'implémentation de la méthode à invoquer pour le super appel n'est pas définie quand vous définissez le trait.
voir le reste de Chapitre 12 pour plus de détails.
Modifier 1 (2013):
il y a une différence subtile dans la façon dont les classes abstraites se comportent par rapport aux traits. Une des règles de linéarisation est qu'elle préserve la hiérarchie héréditaire des classes, ce qui tend à pousser les classes abstraites plus tard dans la chaîne alors que les traits peuvent être heureusement mélangés dans. 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 contraindre la classe de linéarisation (mixin) à la Scala .
Modifier 2 (2018):
à partir de Scala 2.12, le comportement de compatibilité binaire de trait a changé. Avant 2.12, l'ajout ou le retrait d'un membre au trait nécessitait la recompilation de tous les les classes qui héritent du trait, même si les classes n'ont pas changé. Ceci est dû à la façon dont les traits ont été codés dans JVM.
comme de Scala 2.12, traits compiler aux interfaces Java , donc l'exigence a un peu assoupli. Si le caractère présente l'une des caractéristiques suivantes, ses sous-classes doivent encore être recompilées:
- définition de champs (
val
ouvar
, mais une constante est ok –final val
sans type de résultat)- appel à super
- déclarations d'initialiseur dans le corps
- extension d'une classe
- s'appuyant sur la linéarisation pour trouver des implémentations dans le bon supertrait
mais le trait ne le fait pas, vous pouvez maintenant le mettre à jour sans briser la compatibilité binaire.
quoi qu'il en soit, la programmation d'Odersky et al en Scala recommande que, lorsque vous doutez, vous utilisez des traits. Vous pouvez toujours les changer en classes abstraites plus tard si nécessaire.
mis à part le fait que vous ne pouvez pas étendre directement plusieurs classes abstraites, mais vous pouvez mélanger plusieurs traits dans une classe, il est intéressant de mentionner que les traits sont empilables, puisque les super-appels dans un trait sont liés dynamiquement (il s'agit d'une référence à une classe ou trait mélangé avant un courant).
De Thomas de réponse dans la Différence entre une Classe Abstraite et le Trait :
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@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@188c838
scala> yx.a
X
Y
res1: Int = 1
lors de l'extension d'une classe abstraite, cela montre que la sous-classe est d'un type similaire. Ce n'est pas nécessairement le cas lorsqu'on utilise des traits, je pense.
Dans Programmation Scala , disent les auteurs, que les classes abstraites faire un classique orientée objet "est-un" de la relation, tandis que les traits sont un scala sens de la composition.
les classes abstraites peuvent contenir le comportement-elles peuvent paramétrer avec le constructeur args (ce que les traits ne peuvent pas) et représenter une entité de travail. Les Traits représentent plutôt une seule fonctionnalité, une interface d'une fonctionnalité.
- Une classe peut hériter de plusieurs traits, mais seulement une classe abstraite.
- les classes Abstraites peuvent avoir des paramètres du constructeur ainsi que les paramètres de type. Les Traits ne peuvent avoir que des paramètres de type. Par exemple, vous ne pouvez pas dire trait t(I: Int) { }; le paramètre i est illégal.
- les classes abstraites sont totalement interopérables avec Java. Vous pouvez les appeler depuis le code Java sans aucun papier. Les caractéristiques ne sont pleinement interopérables que si elles ne le sont pas. contient n'importe quel code de mise en œuvre.