Comment les mixins ou les traits sont-ils meilleurs que l'héritage multiple?

54

C ++ a un héritage multiple simple, de nombreuses conceptions de langage l'interdisent comme dangereux. Mais certaines langues comme Ruby et PHP utilisent une syntaxe étrange pour faire la même chose et appellent cela mixins ou traits. J'ai souvent entendu dire que les mixins / traits sont plus difficiles à abuser qu'un simple héritage multiple.

Qu'est-ce qui les rend moins dangereux? Y a-t-il quelque chose qui n'est pas possible avec mixins / traits mais possible avec l'héritage multiple de style C ++? Est-il possible de rencontrer le problème du diamant avec eux?

Cela semble être comme si nous utilisions des héritages multiples tout en prétextant que ce sont des mixins / traits afin que nous puissions les utiliser.

Gherman
la source
7
J'attends avec impatience que quelqu'un poste une réponse bien écrite, réfléchie et complète à celle-ci.
Robert Harvey
7
Ruby et PHP n'ont pas introduit les mixins et les traits. Les mixins ont été introduits dans Flavors (1980), tandis que les traits ont été introduits dans Squeak Smalltalk (2001). Flavors était le tout premier langage orienté objet à héritage multiple, et il utilisait des mixins. C ++ n'a hérité que de multiples héritages dans la version 2.0, qui a été publiée en 1989, 9 ans après Flavors. Donc, la question devrait être: Flavors a des mixins simples, mais certains langages tels que C ++ introduisent une syntaxe étrange pour faire la même chose et l'appeler héritage multiple.
Jörg W Mittag

Réponses:

31

Il existe un certain nombre de problèmes liés à l'héritage multiple lorsqu'il est utilisé avec des classes complètes, mais ils tournent tous autour de l' ambiguïté .

L'ambiguïté se manifeste de différentes manières:

  1. Si vous avez deux classes de base avec le même champ xet que le type dérivé demande x, que obtient-il?
    • Si les deux xvariables ont des types incongruents, vous pouvez en déduire.
    • S'ils sont du même type, vous pouvez essayer de les fusionner dans la même variable.
    • Vous pouvez toujours les exposer sous forme de noms qualifiés étranges.
  2. Si vous avez deux classes de base avec la même fonction favec des signatures identiques, et que quelqu'un appelle f, qui appelle?
    • Et si les deux classes de base partagent un autre ancêtre virtuel commun (le problème de diamant).
    • Que se passe-t-il si la fonction a des signatures différentes, mais compatibles?
  3. Lorsque vous construisez une classe avec deux classes de base, quel constructeur de ces classes de base est appelé en premier? Lorsque vous détruisez l'objet, qui est tué?
  4. Lorsque vous disposez l'objet en mémoire, comment le faites-vous de manière cohérente?
  5. Comment gérez-vous tous ces cas avec 3 classes de base? dix?

Et cela ne tient pas compte d'éléments tels que la répartition dynamique, l'inférence de type, la correspondance de modèle et d'autres éléments moins connus qui deviennent plus difficiles à gérer lorsque le langage prend en charge l'héritage multiple de classes complètes.

Les traits ou mix-ins (ou interfaces, ou ...) sont tous des constructions qui limitent spécifiquement les capacités d'un type afin qu'il n'y ait aucune ambiguïté. Ils possèdent rarement quelque chose eux-mêmes. Cela permet d’aplanir la composition des types car il n’ya pas deux variables ou deux fonctions ... il existe une variable et une référence; une fonction et une signature. Le compilateur sait quoi faire.

L’autre approche couramment adoptée consiste à obliger l’utilisateur à "construire" (ou à mélanger) son type, un à la fois. Au lieu que les classes de base soient des partenaires égaux dans le nouveau type, vous ajoutez un type à un autre, en remplaçant tout ce qui y figurait (généralement avec une syntaxe facultative pour renommer et / ou exposer à nouveau les bits remplacés).

Y a-t-il quelque chose qui n'est pas possible avec mixins / traits mais possible avec l'héritage multiple de style C ++?

Selon la langue, il devient généralement difficile ou impossible de fusionner les implémentations de fonctions et le stockage des variables de plusieurs classes de base et de les exposer dans le type dérivé.

Est-il possible de rencontrer des problèmes de diamants avec eux?

Parfois, des variations moins graves apparaîtront en fonction de votre langue, mais généralement pas. L’essentiel des traits est de briser ce type d’ambiguïté.

Telastyn
la source
Pouvez-vous me donner un exemple de langage / technologie permettant de tels types de "bâtiments" pour une instance?
Gherman
@german - Je ne suis certainement pas un expert en Scala, mais d'après ce que j'ai compris, c'est ce que le withmot clé fait ici.
Telastyn
"constructions qui limitent spécifiquement les capacités d'un type de sorte qu'il n'y ait pas d'ambiguïté": quelle sorte de limites? Les interfaces n'ont pas de variables, donc c'est une limite, contrairement aux traits Scala et aux modules Ruby. Sont-ils limités d'une autre manière?
David Moles
@DavidMoles - c'est la méthode habituelle. J'ai également constaté des limitations sur les règles de répartition virtuelle (pensez aux interfaces implémentées explicitement par C #).
Telastyn