La herencia múltiple es una característica de algunos lenguajes de programación informática orientados a objetos en la que un objeto o una clase pueden heredar características de más de un objeto o una clase padre . Se diferencia de la herencia simple, en la que un objeto o una clase solo pueden heredar de un objeto o una clase en particular.
La herencia múltiple ha sido un tema controvertido durante muchos años, [1] [2] con oponentes que señalan su creciente complejidad y ambigüedad en situaciones como el "problema del diamante", donde puede ser ambiguo en cuanto a qué clase padre se hereda una característica particular si más de una clase padre implementa dicha característica. Esto se puede abordar de varias maneras, incluido el uso de herencia virtual . [3] También se han propuesto métodos alternativos de composición de objetos que no se basan en la herencia, como mixins y rasgos , para abordar la ambigüedad.
En programación orientada a objetos (POO), la herencia describe una relación entre dos clases en la que una clase (la clase hija ) es una subclase de la clase padre . La clase hija hereda los métodos y atributos de la clase padre, lo que permite compartir la funcionalidad. Por ejemplo, se podría crear una clase variable Mammal con características como comer, reproducirse, etc.; luego, definir una clase hija Cat que herede esas características sin tener que programarlas explícitamente, mientras agrega nuevas características como perseguir ratones .
La herencia múltiple permite a los programadores utilizar más de una jerarquía totalmente ortogonal simultáneamente, como por ejemplo permitir que Gato herede de Personaje de dibujos animados , Mascota y Mamífero y acceda a características desde dentro de todas esas clases.
Los lenguajes que admiten herencia múltiple incluyen: C++ , Common Lisp (a través de Common Lisp Object System (CLOS)), EuLisp (a través de The EuLisp Object System TELOS), Curl , Dylan , Eiffel , Logtalk , Object REXX , Scala (a través del uso de clases mixin ), OCaml , Perl , POP-11 , Python , R , Raku y Tcl (integrado desde 8.6 o a través de Incremental Tcl ( Incr Tcl ) en versiones anteriores [4] [5] ).
El entorno de ejecución de IBM System Object Model (SOM) admite herencia múltiple, y cualquier lenguaje de programación orientado a SOM puede implementar nuevas clases SOM heredadas de múltiples bases.
Algunos lenguajes orientados a objetos, como Swift , Java , Fortran desde su revisión de 2003 , C# y Ruby implementan herencia única , aunque los protocolos o interfaces proporcionan algunas de las funciones de la verdadera herencia múltiple.
PHP utiliza clases de rasgos para heredar implementaciones de métodos específicos. Ruby utiliza módulos para heredar múltiples métodos.
El " problema del diamante " (a veces denominado el "diamante mortal de la muerte" [6] ) es una ambigüedad que surge cuando dos clases B y C heredan de A, y la clase D hereda tanto de B como de C. Si hay un método en A que B y C han anulado , y D no lo anula, entonces ¿qué versión del método hereda D: la de B o la de C?
Por ejemplo, en el contexto del desarrollo de software de GUI , una clase puede heredar de ambas clases (por su apariencia) y (por su funcionalidad/manejo de entrada), y ambas clases heredan de la clase. Ahora bien, si se llama al método para un objeto y no existe dicho método en la clase, pero hay un método anulado en o (o en ambas), ¿qué método debería ser el que se llame finalmente?Button
Rectangle
Clickable
Rectangle
Clickable
Object
equals
Button
Button
equals
Rectangle
Clickable
Se denomina "problema del diamante" debido a la forma del diagrama de herencia de clases en esta situación. En este caso, la clase A está en la parte superior, tanto B como C por separado debajo de ella, y D une las dos en la parte inferior para formar un diamante.
Los idiomas tienen diferentes formas de abordar estos problemas de herencia repetida.
A
que implementa interfaces Ia
y Ib
con métodos similares que tienen implementaciones predeterminadas tenga dos métodos "heredados" con la misma firma, lo que causa el problema del diamante. Se mitiga ya sea al requerir A
la implementación del método en sí, eliminando así la ambigüedad, o al forzar al llamador a convertir primero el A
objeto a la interfaz adecuada para usar su implementación predeterminada de ese método (por ejemplo, ((Ia) aInstance).Method();
).D
objeto en realidad contendría dos A
objetos separados, y los usos de A
los miembros de tienen que ser calificados correctamente. Si la herencia de A
a B
y la herencia de A
a C
están ambas marcadas como " virtual
" (por ejemplo, " class B : virtual public A
"), C++ tiene especial cuidado de crear solo un A
objeto, y los usos de A
los miembros de funcionan correctamente. Si se mezclan la herencia virtual y la herencia no virtual, hay un único virtual A
y un no virtual A
para cada ruta de herencia no virtual a A
. C++ requiere indicar explícitamente desde qué clase padre se invoca la característica que se va a utilizar, es decir Worker::Human.Age
, . C++ no admite la herencia repetida explícita, ya que no habría forma de calificar qué superclase utilizar (es decir, que una clase aparezca más de una vez en una única lista de derivación [class Dog : public Animal, Animal]). C++ también permite crear una única instancia de la clase múltiple a través del mecanismo de herencia virtual (es decir, Worker::Human
y Musician::Human
hará referencia al mismo objeto).D,B,C,A
, cuando B se escribe antes de C en la definición de clase. Se elige el método con las clases de argumentos más específicas (D>(B,C)>A); luego en el orden en que se nombran las clases padre en la definición de subclase (B>C). Sin embargo, el programador puede anular esto, dando un orden de resolución de método específico o indicando una regla para combinar métodos. Esto se llama combinación de métodos, que puede controlarse completamente. El MOP ( protocolo de metaobjetos ) también proporciona medios para modificar la herencia, el envío dinámico , la instanciación de clases y otros mecanismos internos sin afectar la estabilidad del sistema.D
incorpora dos estructuras B
y C
que tienen un método F()
, por lo que satisfacen una interfaz A
, el compilador se quejará de un "selector ambiguo" si D.F()
se llama a , o si D
se asigna una instancia de a una variable de tipo A
. Los métodos de B
y C
se pueden llamar explícitamente con D.B.F()
o D.C.F()
.A,B,C
son interfaces, B,C
cada una puede proporcionar una implementación diferente a un método abstracto de A
, lo que provoca el problema del diamante. Cualquiera de las clases D
debe reimplementar el método (cuyo cuerpo puede simplemente reenviar la llamada a una de las superimplementaciones), o la ambigüedad será rechazada como un error de compilación. [8] Antes de Java 8, Java no estaba sujeto al riesgo del problema del diamante, porque no admitía herencia múltiple y los métodos predeterminados de la interfaz no estaban disponibles.(individual as Person).printInfo();
. super<ChosenParentInterface>.someMethod()
B
y sus antecesores se comprobarían antes que la clase C
y sus antecesores, por lo que el método en A
se heredaría a través de B
. Esto se comparte con Io y Picolisp . En Perl, este comportamiento se puede anular utilizando mro
u otros módulos para utilizar la linealización C3 u otros algoritmos. [9]object
. Python crea una lista de clases utilizando el algoritmo de linealización C3 (o Orden de Resolución de Métodos (MRO)). Ese algoritmo impone dos restricciones: los hijos preceden a sus padres y si una clase hereda de múltiples clases, se mantienen en el orden especificado en la tupla de clases base (sin embargo, en este caso, algunas clases altas en el gráfico de herencia pueden preceder a las clases más bajas en el gráfico [10] ). Por lo tanto, el orden de resolución de métodos es: D
, B
, C
, A
. [11]D
, C
, A
, B
, A
], que se reduce a [ D
, C
, B
, A
].Los lenguajes que solo permiten una herencia simple , donde una clase solo puede derivar de una clase base, no tienen el problema del diamante. La razón de esto es que dichos lenguajes tienen como máximo una implementación de cualquier método en cualquier nivel de la cadena de herencia independientemente de la repetición o ubicación de los métodos. Normalmente, estos lenguajes permiten que las clases implementen múltiples protocolos , llamados interfaces en Java. Estos protocolos definen métodos pero no proporcionan implementaciones concretas. Esta estrategia ha sido utilizada por ActionScript , C# , D , Java , Nemerle , Object Pascal , Objective-C , Smalltalk , Swift y PHP . [13] Todos estos lenguajes permiten que las clases implementen múltiples protocolos.
Además, Ada , C#, Java, Object Pascal, Objective-C, Swift y PHP permiten la herencia múltiple de interfaces (llamadas protocolos en Objective-C y Swift). Las interfaces son como clases base abstractas que especifican firmas de métodos sin implementar ningún comportamiento. (Las interfaces "puras" como las de Java hasta la versión 7 no permiten ninguna implementación o datos de instancia en la interfaz). Sin embargo, incluso cuando varias interfaces declaran la misma firma de método, tan pronto como ese método se implementa (define) en cualquier parte de la cadena de herencia, anula cualquier implementación de ese método en la cadena superior (en sus superclases). Por lo tanto, en cualquier nivel dado en la cadena de herencia, puede haber como máximo una implementación de cualquier método. Por lo tanto, la implementación de un método de herencia única no presenta el Problema de Diamante incluso con herencia múltiple de interfaces. Con la introducción de la implementación predeterminada para interfaces en Java 8 y C# 8, aún es posible generar un Problema de Diamante, aunque esto solo aparecerá como un error de tiempo de compilación.