(Remarque: j'ai utilisé "erreur" au lieu de "problème" dans le titre pour des raisons évidentes ..;)).
J'ai fait quelques lectures de base sur les traits à Scala. Ils sont similaires aux interfaces dans Java ou C #, mais ils permettent l'implémentation par défaut d'une méthode.
Je me demandais: cela ne peut-il pas provoquer un cas du "problème du diamant", c'est pourquoi de nombreuses langues évitent en premier lieu l'héritage multiple?
Si oui, comment Scala gère-t-il cela?
Le problème du diamant est l'incapacité de décider quelle mise en œuvre de la méthode choisir. Scala résout ce problème en définissant l'implémentation à choisir dans le cadre des spécifications du langage ( lire la partie sur Scala dans cet article Wikipedia ).
Bien sûr, la même définition d'ordre pourrait également être utilisée dans l'héritage multiple de classe, alors pourquoi s'embêter avec des traits?
La raison pour laquelle l'OMI est les constructeurs. Les constructeurs ont plusieurs limitations que les méthodes normales n'ont pas - ils ne peuvent être appelés qu'une seule fois par objet, ils doivent être appelés pour chaque nouvel objet, et le constructeur d'une classe enfant doit appeler le constructeur de son parent comme première instruction (la plupart des langages le faire implicitement pour vous si vous n'avez pas besoin de passer de paramètres).
Si B et C héritent de A et D héritent de B et C, et que les constructeurs de B et de C appellent le constructeur de A, alors le constructeur de D appellera le constructeur de A deux fois. Définir les implémentations à choisir comme Scala l'a fait avec les méthodes ne fonctionnera pas ici car les deux constructeurs B et C doivent être appelé.
Les traits évitent ce problème car ils n'ont pas de constructeurs.
Scala évite le problème du diamant par quelque chose appelé "linéarisation des traits". Fondamentalement, il recherche l'implémentation de la méthode dans les traits que vous étendez de droite à gauche. Exemple simple:
trait Base {
def op: String
}
trait Foo extends Base {
override def op = "foo"
}
trait Bar extends Base {
override def op = "bar"
}
class A extends Foo with Bar
class B extends Bar with Foo
(new A).op
// res0: String = bar
(new B).op
// res1: String = foo
Cela dit, la liste des traits qu'il recherche peut contenir plus que ceux que vous avez explicitement donnés, car ils pourraient étendre d'autres traits. Une explication détaillée est donnée ici: Traits en tant que modifications empilables et un exemple plus complet de la linéarisation ici: Pourquoi pas l'héritage multiple?
Je crois que dans d'autres langages de programmation, ce comportement est parfois appelé "ordre de résolution de méthode" ou "MRO".