¿Cómo son los mixins o los rasgos mejores que la herencia múltiple simple?

54

C ++ tiene una herencia múltiple simple, muchos diseños de lenguaje lo prohíben como peligroso. Pero algunos lenguajes como Ruby y PHP usan una sintaxis extraña para hacer lo mismo y llamarlo mixins o rasgos. Muchas veces escuché que los mixins / rasgos son más difíciles de abusar que la simple herencia múltiple.

¿Qué los hace específicamente menos peligrosos? ¿Hay algo que no es posible con mixins / rasgos pero posible con herencia múltiple estilo C ++? ¿Es posible encontrarse con el problema del diamante con ellos?

Esto parece como si estuviéramos usando herencia múltiple, pero solo inventando excusas de que esos son mixins / rasgos para que podamos usarlos.

Gherman
fuente
77
Espero que alguien publique una respuesta bien escrita, reflexiva y exhaustiva a esta.
Robert Harvey
77
Ruby y PHP no introdujeron mixins y rasgos. Los mixins se introdujeron en Flavors (1980), los rasgos se introdujeron en Squeak Smalltalk (2001). Flavors fue el primer lenguaje orientado a objetos con herencia múltiple, y usó mixins. C ++ solo obtuvo herencia múltiple en la versión 2.0, que se lanzó en 1989, 9 años después de Flavors. Entonces, la pregunta debería ser: Flavors tiene mixins simples, pero algunos lenguajes como C ++ introducen una sintaxis extraña para hacer lo mismo y llamarla herencia múltiple.
Jörg W Mittag

Respuestas:

31

Hay una serie de problemas con la herencia múltiple cuando se usa con clases completas, pero todas giran en torno a la ambigüedad .

La ambigüedad se muestra de diferentes maneras:

  1. Si tiene dos clases base con el mismo campo x, y el tipo derivado pregunta x, ¿qué obtiene?
    • Si las dos xvariables tienen tipos incongruentes, podría inferirlo.
    • Si son del mismo tipo, podría intentar fusionarlos en la misma variable.
    • Siempre puedes exponerlos como nombres raros y totalmente calificados.
  2. Si tiene dos clases base con la misma función fcon firmas idénticas y alguien llama f, ¿a quién se llama?
    • ¿Qué pasa si las dos clases base comparten otro ancestro virtual común (el problema del diamante)?
    • ¿Qué pasa si la función tiene firmas diferentes pero compatibles?
  3. Cuando construyes una clase con dos clases base, ¿cuál de los constructores de las clases base se llama primero? Cuando destruyes el objeto, ¿cuál es asesinado?
  4. Cuando coloca el objeto en la memoria, ¿cómo lo hace de manera consistente?
  5. ¿Cómo manejas todos estos casos con 3 clases base? 10?

Y eso ignora cosas como el despacho dinámico, la inferencia de tipos, la coincidencia de patrones y otras cosas sobre las que sé menos y que se vuelven más desafiantes cuando el lenguaje admite la herencia múltiple de clases completas.

Los rasgos o Mix-ins (o interfaces, o ...) son construcciones que limitan específicamente las capacidades de un tipo para que no haya ambigüedad. Raramente poseen algo ellos mismos. Esto permite que la composición de los tipos sea más suave porque no hay dos variables o dos funciones ... hay una variable y una referencia; una función y una firma. El compilador sabe qué hacer.

El otro enfoque común adoptado es obligar al usuario a "construir" (o mezclar) su tipo de uno en uno. En lugar de que las clases base sean socios iguales en el nuevo tipo, agrega un tipo a otro, anulando todo lo que estaba allí (generalmente con sintaxis opcional para renombrar y / o volver a exponer los bits anulados).

¿Hay algo que no es posible con mixins / rasgos pero posible con herencia múltiple estilo C ++?

Dependiendo del idioma, generalmente se vuelve problemático o imposible combinar implementaciones de funciones y almacenamiento para variables de múltiples clases base y exponerlas en el tipo derivado.

¿Es posible encontrarse con un problema de diamantes con ellos?

Ocasionalmente, aparecerán variaciones menos severas en función de su idioma, pero generalmente no. El punto completo de los rasgos es romper ese tipo de ambigüedad.

Telastyn
fuente
¿Me puede dar un ejemplo del lenguaje / tecnología que permite tales tipos de "construcción" para una instancia?
Gherman
@german: ciertamente no soy un experto en Scala, pero entiendo que eso es lo que hace la withpalabra clave allí.
Telastyn
"construcciones que limitan específicamente las capacidades de un tipo para que no haya ambigüedad": ¿qué tipo de límites? Las interfaces no tienen variables, por lo que ese es un límite, pero los rasgos Scala y los módulos Ruby sí. ¿Están limitados de otras maneras?
David Moles
@DavidMoles: esa es la forma común, también he visto limitaciones en las reglas de despacho virtuales (piense en las interfaces implementadas explícitamente de C #).
Telastyn