Herencia múltiple
La herencia múltiple es una característica de algunos lenguajes de programación informáticos orientados a objetos en los que un objeto o clase puede heredar características de más de un objeto o clase principal. Es diferente de la herencia única, donde un objeto o clase solo puede heredar de un objeto o clase en particular.
La herencia múltiple ha sido un tema controvertido durante muchos años, y los opositores señalan su mayor complejidad y ambigüedad en situaciones como el "problema del diamante", donde puede ser ambiguo en cuanto a qué clase padre un determinado La característica se hereda si más de una clase principal implementa dicha característica. Esto se puede abordar de varias maneras, incluido el uso de la herencia virtual. 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.
Detalles
En la programación orientada a objetos (POO), la herencia describe una relación entre dos clases en las que una clase (la clase secundaria) subclases la clase padre. El hijo hereda 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 Gato que herede esas funciones sin tener que programarlas explícitamente, mientras agrega nuevas funciones 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 Gato herede de Personaje de dibujos animados y Mascota y Mamífero y acceder a funciones desde dentro de todas esas clases.
Implementaciones
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 mixtas), 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).
El tiempo de ejecución de IBM System Object Model (SOM) admite la herencia múltiple, y cualquier lenguaje de programación destinado a SOM puede implementar nuevas clases de SOM heredadas de varias 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 parte de la funcionalidad 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
El "problema del diamante" (a veces denominado "Diamante mortal de la muerte") 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 Button
puede heredar de ambas clases Rectangle
(por apariencia) y Clickable
(para funcionalidad/manejo de entrada), y las clases Rectangle
y Clickable
heredan de la clase Object
. Ahora, si se llama al método equals
para un objeto Button
y no existe tal método en la clase Button
pero hay un anulado equals
en Rectangle
o Clickable
(o ambos), ¿a qué método debería llamarse finalmente?
Se llama el "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 una forma de diamante.
Mitigación
Los lenguajes tienen diferentes formas de lidiar con estos problemas de herencia repetida.
- C# (desde C# 8.0) permite la implementación por defecto del método de interfaz, causando una clase
A
, interfaces de implementaciónIa
yIb
con métodos similares que tienen implementaciones predeterminadas, tener dos métodos "heredados" con la misma firma, causando el problema del diamante. Es mitigado por tenerA
para implementar el método en sí, eliminando así la ambigüedad, o obligando al llamador a lanzar primero elA
objetar a la interfaz adecuada para utilizar su aplicación predeterminada de ese método (por ejemplo,((Ia) aInstance).Method();
). - C++ por defecto sigue cada ruta de herencia por separado, por lo que
D
el objeto realmente contiene dosA
objetos y usos deA
Los miembros tienen que estar debidamente calificados. Si la herencia deA
aB
y la herencia deA
aC
ambos marcados "virtual
" (por ejemplo, "class B: virtual public A
"), C++ tiene especial cuidado para crear unoA
y usos deA
Los miembros trabajan correctamente. Si la herencia virtual y la herencia no virtual se mezclan, hay un solo virtualA
, y un no virtualA
para cada ruta de herencia no virtualA
. C++ requiere indicar explícitamente qué clase padre la característica a utilizar se invoca de i.e.Worker::Human.Age
. C++ no apoya la herencia repetida explícita ya que no habría manera de calificar qué superclase utilizar (es decir, tener una clase aparece más de una vez en una lista de derivación única [clase Dog: public Animal, Animal]). C++ también permite crear una sola instancia de la clase múltiple a través del mecanismo de herencia virtual (es decir,.Worker::Human
yMusician::Human
hará referencia al mismo objeto). - Common Lisp CLOS intenta proporcionar tanto el comportamiento predeterminado razonable como la capacidad de anularlo. Por defecto, para decirlo simplemente, los métodos se clasifican
D,B,C,A
, cuando B está escrito antes de C en la definición de clase. El método con las clases de argumentos más específicas es elegido (D titulado(B,C)]; luego en el orden en el que las clases de padres son nombradas en la definición de subclase (B tituladoC). Sin embargo, el programador puede anular esto, dando un orden específico de resolución de método o estableciendo una regla para combinar métodos. Esto se llama combinación de método, que puede ser totalmente controlado. El MOP (protocolo de metaobjeto) también proporciona medios para modificar la herencia, envío dinámico, instantánea de clase y otros mecanismos internos sin afectar la estabilidad del sistema. - Curl permite sólo clases que están explícitamente marcadas compartido ser heredada repetidamente. Las clases compartidas deben definir un constructor secundario para cada constructor regular de la clase. El constructor regular se llama la primera vez que el estado para la clase compartida se inicializa a través de un constructor de subclase, y el constructor secundario será invocado para todas las otras subclases.
- En Eiffel, las características de los antepasados se eligen explícitamente con directivas selectas y renombradas. Esto permite que las características de la clase base sean compartidas entre sus descendientes o darles a cada uno una copia separada de la clase base. Eiffel permite la unión explícita o separación de características heredadas de clases de ancestro. Eiffel se unirá automáticamente a las características juntas, si tienen el mismo nombre y aplicación. El escritor de clase tiene la opción de renombrar las características heredadas para separarlas. La herencia múltiple es una ocurrencia frecuente en el desarrollo Eiffel; la mayoría de las clases efectivas en la biblioteca de datos y algoritmos ampliamente utilizado, por ejemplo, tienen dos o más padres.
- Ir evita el problema del diamante en el tiempo de compilación. Si una estructura
D
dos estructurasB
yC
que ambos tienen un métodoF()
, así satisfacer una interfazA
, el compilador se quejará de un "e selector ambicioso" siD.F()
se llama, o si una instanciaD
se asigna a una variable de tipoA
.B
yC
's métodos se pueden llamar explícitamenteD.B.F()
oD.C.F()
. - Java 8 introduce métodos predeterminados en interfaces. Si
A,B,C
son interfaces,B,C
puede cada uno proporcionar una aplicación diferente a un método abstractoA
, causando el problema del diamante. Cualquier claseD
debe reimplementar el método (el cuerpo del cual simplemente puede enviar la llamada a una de las super implementaciones), o la ambigüedad será rechazada como un error compilado. Antes de Java 8, Java no estaba sujeto al riesgo de problema de Diamantes, porque no soportaba múltiples métodos de herencia e interfaz por defecto no estaban disponibles. - JavaFX Script en la versión 1.2 permite múltiples herencias a través del uso de mezclas. En caso de conflicto, el compilador prohíbe el uso directo de la variable o función ambigua. Cada miembro heredado todavía puede ser accedido lanzando el objeto a la mezcla de interés, por ejemplo.
(individual as Person).printInfo();
. - Kotlin permite la sucesión múltiple de Interfaces, sin embargo, en un escenario de problema de Diamantes, la clase infantil debe anular el método que causa el conflicto de herencia y especificar qué implementación de la clase padre debe ser utilizado. eg
super.someMethod()
- Logtalk admite la multiherencia de interfaz e implementación, permitiendo la declaración del método alias que proporcionen tanto el renombramiento como el acceso a métodos que serían enmascarados por el mecanismo de resolución de conflictos predeterminado.
- En OCaml, las clases de padres se especifican individualmente en el cuerpo de la definición de clase. Los métodos (y atributos) se heredan en el mismo orden, con cada método recién heredado que anula cualquier método existente. OCaml elige la última definición coincidente de una lista de herencia de clase para resolver qué método de implementación utilizar bajo ambigüedades. Para anular el comportamiento predeterminado, uno simplemente califica una llamada de método con la definición de clase deseada.
- Perl utiliza la lista de clases para heredar como una lista ordenada. El compilador utiliza el primer método que encuentra mediante la búsqueda de la lista de superclase o utilizando la linealización C3 de la jerarquía de clases. Varias extensiones proporcionan esquemas de composición de clase alternativos. El orden de la herencia afecta a la semántica de clase. En la ambigüedad anterior, clase
B
y sus antepasados se verían antes de claseC
y sus antepasados, así el métodoA
se heredaría medianteB
. Esto es compartido con Io y Picolisp. En Perl, este comportamiento puede ser anulado usando elmro
u otros módulos para usar linearización C3 u otros algoritmos. - Python tiene la misma estructura que Perl, pero, a diferencia de Perl, la incluye en la sintaxis del idioma. El orden de la herencia afecta a la semántica de clase. Python tuvo que lidiar con esto después de la introducción de clases de nuevo estilo, todas ellas con un ancestro común,
object
. Python crea una lista de clases usando el algoritmo de linearización C3 (o Orden de Resolución de Métodos (MRO). Ese algoritmo impone dos limitaciones: los niños preceden a sus padres y si una clase hereda de múltiples clases, se mantienen en el orden especificado en el tuple de clases base (aunque en este caso, algunas clases altas en el gráfico de la herencia pueden preceder a clases inferiores en el gráfico). Así, la orden de resolución del método es:D
,B
,C
,A
. - Las clases de Ruby tienen exactamente un padre pero también pueden heredar de múltiples módulos; Las definiciones de clase de rubí se ejecutan, y la (re)definición de un método oscurece cualquier definición ya existente en el momento de la ejecución. En ausencia de metaprogramación de tiempo de ejecución esto tiene aproximadamente la misma semántica que la primera resolución más derecha de profundidad.
- Scala permite múltiples instantáneas rasgos, que permite la herencia múltiple agregando una distinción entre la jerarquía de clases y la jerarquía de rasgos. Una clase sólo puede heredar de una sola clase, pero puede mezclar en tantos rasgos como se desee. Scala resuelve los nombres de los métodos utilizando una primera búsqueda de 'traits' ampliada, antes de eliminar todo pero la última ocurrencia de cada módulo en la lista resultante. Por lo tanto, la orden de resolución es: [
D
,C
,A
,B
,A
], que reduce a [D
,C
,B
,A
]. - Tcl permite múltiples clases de padres; el orden de especificación en la declaración de clase afecta la resolución de nombres para los miembros usando el algoritmo de linearización C3.
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. Por lo general, 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. 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 (llamados 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 está implementado (definido) en cualquier parte de la cadena de herencia, anula cualquier implementación de ese método en la cadena anterior (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 la 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 solo aparecerá como un error en tiempo de compilación.
Contenido relacionado
Licencia MIT
Plataforma informática
Abstracción (ciencias de la computación)