394 votes

Quel est l'avantage d'utiliser des classes abstraites plutôt que des traits ?

Quel est l'avantage d'utiliser une classe abstraite au lieu d'un trait (à part les performances) ? Il semble que les classes abstraites puissent être remplacées par des traits dans la plupart des cas.

395voto

Mushtaq Ahmed Points 2518

Je peux penser à deux différences

  1. Les classes abstraites peuvent avoir des paramètres de constructeur ainsi que des paramètres de type. Les traits ne peuvent avoir que des paramètres de type. Il a été discuté que dans le futur, même les traits peuvent avoir des paramètres de constructeur.
  2. Les classes abstraites sont totalement interopérables avec Java. Vous pouvez les appeler à partir du code Java sans aucun wrapper. Les traits sont totalement interopérables uniquement s'ils ne contiennent pas de code d'implémentation.

186 votes

Addendum très important : Une classe peut hériter de plusieurs traits mais d'une seule classe abstraite. Je pense que cela devrait être la première question qu'un développeur se pose lorsqu'il réfléchit au choix à faire dans presque tous les cas.

18 votes

Lifesaver : "Les traits ne sont totalement interopérables que s'ils ne contiennent pas de code d'implémentation".

2 votes

Abstrait - lorsque des comportements collectifs définissent ou mènent à un objet (branche de l'objet) mais ne sont pas encore composés pour être en tant qu'objet (prêt). Traits, lorsque vous avez besoin d'induire des capacités, c'est-à-dire que les capacités ne naissent jamais avec la création de l'objet, elles évoluent ou sont requises lorsqu'un objet sort de son isolement et doit communiquer.

222voto

Eugene Yokota Points 43213

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.

2 votes

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 ?

2 votes

@0fnt Sa distinction n'est pas sur les extensions vs avec. Ce qu'il dit, c'est que si vous ne mélangez le trait que dans la même compilation, les problèmes de compatibilité binaire ne s'appliquent pas. Cependant, si votre API est conçue pour permettre aux utilisateurs de mélanger le trait eux-mêmes, alors vous devrez vous soucier de la compatibilité binaire.

2 votes

@0fnt : Il n'y a absolument aucune différence sémantique entre extends y with . Il 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 .

80voto

Daniel C. Sobral Points 159554

Pour ce que ça vaut, l'étude d'Odersky et al. Programmation en Scala recommande que, quand on doute, on utilise des traits. Vous pourrez toujours les transformer en classes abstraites par la suite, si nécessaire.

21voto

Nemanja Boric Points 11853

Outre le fait que vous ne pouvez pas étendre directement plusieurs classes abstraites, mais que vous pouvez mixer plusieurs traits dans une classe, il convient de mentionner que les traits sont empilables, puisque les appels de super dans un trait sont liés dynamiquement (il s'agit de renvoyer une classe ou un trait mixé avant la classe actuelle).

D'après la réponse de Thomas dans Différence entre classe abstraite et 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$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

10voto

peter p Points 469

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.

0 votes

Cela a-t-il des implications pratiques ou cela ne fait-il que rendre le code plus facile à comprendre ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X