La herencia múltiple es una característica de algunos lenguajes de programación de computadoras orientados a objetos en los que un objeto o clase puede heredar características de más de un objeto o clase principal . Es distinta de la herencia única, donde un objeto o clase sólo puede heredar de un objeto o clase en particular.
La herencia múltiple ha sido un tema controvertido durante muchos años, [1] [2] y sus oponentes señalan su mayor complejidad y ambigüedad en situaciones como el "problema del diamante", donde puede ser ambiguo en cuanto a qué clase de padre es una característica particular. heredado de si más de una clase principal 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 secundaria ) es una subclase de la clase principal . El hijo hereda los métodos y atributos del padre, lo que permite una funcionalidad compartida. Por ejemplo, se podría crear una clase variable Mamífero con características como comer, reproducirse, etc.; luego defina una clase secundaria 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 usar más de una jerarquía totalmente ortogonal simultáneamente, como permitir que Cat herede de un personaje de dibujos animados y de una mascota y un mamífero y acceda a funciones desde 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 (mediante el uso de clases mixin ), OCaml , Perl , POP-11 , Python , R , Raku y Tcl (integrados desde 8.6 o mediante Incremental Tcl ( Incr Tcl ) en versiones anteriores [4] [5] ).
El tiempo de ejecución de IBM System Object Model (SOM) admite herencia múltiple y cualquier lenguaje de programación dirigido 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 funcionalidades de la verdadera herencia múltiple.
PHP usa clases de rasgos para heredar implementaciones de métodos específicos. Ruby usa 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 GUI , una clase puede heredar tanto de clases (para apariencia) como (para funcionalidad/manejo de entradas) y clases y ambas heredan de la clase. Ahora bien, si se llama al método para un objeto y no existe tal método en la clase pero hay un método anulado en o (o ambos), ¿qué método debería llamarse finalmente?Button
Rectangle
Clickable
Rectangle
Clickable
Object
equals
Button
Button
equals
Rectangle
Clickable
Se le llama "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 los dos en la parte inferior para formar una forma de diamante.
Las lenguas tienen diferentes maneras 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 provoca el problema del diamante. Se mitiga solicitando A
la implementación del método en sí, eliminando así la ambigüedad, o obligando a la persona que llama a enviar 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 ' deben calificarse adecuadamente. Si la herencia de A
hasta B
y la herencia de A
hasta C
están marcadas como " virtual
" (por ejemplo, " class B : virtual public A
"), C++ tiene especial cuidado en 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 una única ruta de herencia virtual A
y una no virtual A
para cada ruta de herencia no virtual A
. C++ requiere indicar explícitamente desde qué clase principal se invoca la característica que se utilizará, es decir Worker::Human.Age
. C++ no admite herencia repetida explícita ya que no habría forma de calificar qué superclase usar (es decir, hacer que una clase aparezca más de una vez en una única lista de derivación [clase Perro: Animal público, 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 principales 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 estableciendo una regla para combinar métodos. Esto se denomina combinación de métodos y puede controlarse por completo. El MOP ( protocolo de metaobjeto ) también proporciona medios para modificar la herencia, el despacho dinámico , la creación de instancias de clases y otros mecanismos internos sin afectar la estabilidad del sistema.D
incorpora dos estructuras B
y C
ambas tienen un método F()
, satisfaciendo así una interfaz A
, el compilador se quejará de un "selector ambiguo" si D.F()
se llama, o si D
se asigna una instancia de a una variable de tipo A
. Los métodos de B
and 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 , A
lo que causa el problema del diamante. Cualquiera de las clases D
debe volver a implementar 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 Diamond, porque no admitía herencia múltiple y los métodos predeterminados de interfaz no estaban disponibles.(individual as Person).printInfo();
. super<ChosenParentInterface>.someMethod()
B
y sus ancestros se verificarían antes que la clase C
y sus ancestros, por lo que el método en A
se heredaría a través de B
. Este 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 (u 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 varias 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 clases inferiores en el gráfico [10] ). Por tanto, el orden de resolución del método es: D
, B
, C
, A
. [11]D
, C
, A
, ], que se reduce a [ , , , B
] .A
D
C
B
A
Los lenguajes que solo permiten herencia única , 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 de la cadena de herencia, puede haber como máximo una implementación de cualquier método. Por lo tanto, la implementación del método de herencia única no presenta el problema del diamante incluso con herencia múltiple de interfaces. Con la introducción de la implementación predeterminada para las interfaces en Java 8 y C# 8, todavía es posible generar un problema Diamond, aunque esto sólo aparecerá como un error en tiempo de compilación.