Lorsque googlé, de nombreuses réponses à ce sujet apparaissent. Cependant, je ne pense pas que l'un d'entre eux illustre bien la différence entre ces deux fonctionnalités. Je voudrais donc essayer une fois de plus, en particulier ...
Que peut-on faire avec les auto-types et non avec l'héritage, et vice-versa?
Pour moi, il devrait y avoir une différence physique quantifiable entre les deux, sinon ils sont juste nominalement différents.
Si le caractère A prolonge B ou se self-types B, n'illustrent-ils pas tous les deux qu'être de B est une exigence? Quelle est la différence?
design-patterns
object-oriented-design
scala
Mark Canlas
la source
la source
Réponses:
Si le trait A prolonge B, alors le mélange dans A vous donne précisément B plus tout ce que A ajoute ou étend. En revanche, si trait A a une référence auto qui est explicitement typé B, puis la classe mère ultime doit également mélanger B ou un type descendant de B (et mélanger en premier , ce qui est important).
C'est la différence la plus importante. Dans le premier cas, le type précis de B est cristallisé au point A où il se prolonge. Dans le second, le concepteur de la classe parent doit décider quelle version de B est utilisée, au moment où la classe parent est composée.
Une autre différence est où A et B fournissent des méthodes du même nom. Lorsque A étend B, la méthode de A remplace B. Lorsque A est mélangé après B, la méthode de A gagne simplement.
L'auto référence saisie vous donne beaucoup plus de liberté; le couplage entre A et B est lâche.
MISE À JOUR:
Puisque vous n'êtes pas clair sur les avantages de ces différences ...
Si vous utilisez l'héritage direct, vous créez le trait A qui est B + A. Vous avez fixé la relation dans la pierre.
Si vous utilisez une auto-référence typée, toute personne qui souhaite utiliser votre trait A dans la classe C pourrait
Et ce n'est pas la limite de leurs options, étant donné la façon dont Scala vous permet d'instancier un trait directement avec un bloc de code comme constructeur.
Quant à la différence entre la méthode gagnante de A , parce que A est mélangé en dernier, par rapport à A qui étend B, considérez ceci ...
Lorsque vous mélangez dans une séquence de traits, chaque fois que la méthode
foo()
est invoquée, le compilateur va au dernier trait mélangé à rechercherfoo()
, puis (s'il n'est pas trouvé), il parcourt la séquence vers la gauche jusqu'à ce qu'il trouve un trait qui implémentefoo()
et utilise cette. A a également la possibilité d'appelersuper.foo()
, qui parcourt également la séquence vers la gauche jusqu'à ce qu'il trouve une implémentation, etc.Donc, si A a une auto-référence typée à B et que l'auteur de A sait que B implémente
foo()
, A peut appelersuper.foo()
sachant que si rien d'autre ne le prévoitfoo()
, B le fera. Cependant, le créateur de la classe C a la possibilité de supprimer tout autre trait dans lequel il est implémentéfoo()
, et A l'obtiendra à la place.Encore une fois, c'est beaucoup plus puissant et moins limitatif que A étendant B et appelant directement la version de B de
foo()
.la source
J'ai du code qui illustre certaines des différences par rapport à la visibilité et aux implémentations "par défaut" lors de l'extension par rapport à la définition d'un auto-type. Il n'illustre aucune des parties déjà discutées sur la façon dont les collisions de noms réelles sont résolues, mais se concentre plutôt sur ce qui est possible et impossible de le faire.
Une différence importante de l'OMI est qu'elle
A1
n'expose pas qu'elle est nécessaireB
à tout ce qui la voit simplementA1
(comme illustré dans les parties montées). Le seul code qui verra réellement qu'une spécialisation particulièreB
est utilisée est un code qui connaît explicitement le type composé (commeThink*{X,Y}
).Un autre point est que
A2
(avec extension) utilisera réellementB
si rien d'autre n'est spécifié tandis queA1
(self-type) ne dit pas qu'il utilisera àB
moins qu'il ne soit surchargé, un B concret doit être explicitement donné lorsque les objets sont instanciés.la source