Articulo de referencia

Herencia múltiple

La herencia múltiple es una característica de algunos lenguajes de programación orientados a objetos en la que un objeto o clase puede heredar características de más de un objet...

La herencia múltiple es una característica de algunos lenguajes de programación orientados a objetos en la que un objeto o clase puede heredar características de más de un objeto o clase padre . Se diferencia de la herencia simple, donde un objeto o clase solo puede heredar de un único objeto o clase.

La herencia múltiple ha sido un tema controvertido durante muchos años, [ 1 ] [ 2 ] y sus detractores señalan su mayor complejidad y ambigüedad en situaciones como el "problema del diamante", donde puede resultar ambiguo determinar de qué clase padre se hereda una característica particular si más de una clase padre implementa dicha característica. Esto puede abordarse de diversas maneras, incluyendo el uso de la herencia virtual . [ 3 ] También se han propuesto métodos alternativos de composición de objetos no basados ​​en la herencia, como los mixins y los traits, para abordar la ambigüedad.

Detalles

En la programación orientada a objetos (POO), la herencia describe una relación entre dos clases en la que una clase (la clase hija ) hereda (o extiende ) de la clase padre . La clase hija hereda los métodos y atributos de la clase padre, lo que permite compartir funcionalidades. Por ejemplo, se podría crear una clase variable con características como comer, reproducirse, etc.; luego definir una clase hija que herede esas características sin tener que programarlas explícitamente, a la vez que se añaden nuevas características como perseguir ratones .MammalCat

La herencia múltiple permite a los programadores usar más de una jerarquía totalmente ortogonal simultáneamente, como por ejemplo, permitir extender y Catacceder CartoonCharactera características desde dentro de todas esas clases.PetMammal

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 mixin ), OCaml , Perl , POP-11 , Python , R , Raku y Tcl (integrado desde la versión 8.6 o a través de Tcl incremental ( Incr Tcl ) en versiones anteriores [ 4 ] [ 5 ] ).

El entorno de ejecución de IBM System Object Model (SOM) admite la herencia múltiple, y cualquier lenguaje de programación que tenga como objetivo SOM puede implementar nuevas clases SOM que hereden de múltiples clases base.

Algunos lenguajes orientados a objetos, como Swift , Java , Fortran ( desde su revisión de 2003) , C# y Ruby , implementan la herencia simple , aunque los protocolos o interfaces proporcionan parte de la funcionalidad 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

Diagrama de herencia de clases de diamante

El " problema del diamante " (a veces denominado "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 de ambas. Si existe un método en A que B y C han sobrescrito , y D no lo sobrescribe, ¿de qué versión del método hereda D: de B o de C?

Por ejemplo, en el contexto del desarrollo de software GUI , una clase puede heredar de ambas clases (para la apariencia) y (para la funcionalidad/manejo de entrada), y las clases y 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 sobrescrito en o (o en ambos), ¿qué método debería llamarse finalmente?ButtonRectangleClickableRectangleClickableObjectequalsButtonButtonequalsRectangleClickable

Se le conoce como el "problema del diamante" debido a la forma del diagrama de herencia de clases en esta situación. En este caso, la clase A se encuentra en la parte superior, B y C se ubican por separado debajo de ella, y D las une en la parte inferior para formar un diamante.

Mitigación

Los idiomas tienen diferentes maneras de abordar estos problemas de herencia repetida.

  • C# (desde C# 8.0) permite la implementación predeterminada de métodos de interfaz, lo que provoca que una clase A, que implementa interfaces Iay Ibtiene métodos similares con implementaciones predeterminadas, tenga dos métodos "heredados" con la misma firma, causando el problema del diamante. Esto se mitiga exigiendo Aque se implemente el método en sí, eliminando así la ambigüedad, o forzando al llamador a convertir primero el Aobjeto a la interfaz apropiada para usar su implementación predeterminada de ese método (por ejemplo ((Ia) aInstance).Method();).
  • C++ por defecto sigue cada ruta de herencia por separado. Por lo tanto, dado el problema del diamante, un Dobjeto en realidad contendría dos Aobjetos separados, y los usos de Alos miembros de deben estar debidamente calificados. Si la herencia de Aa By la herencia de Aa Cestán ambas marcadas " virtual" (por ejemplo, " "), C++ se asegura de crear solo un objeto, y los usos de los miembros de funcionan correctamente. Si se mezclan la herencia virtual y la herencia no virtual, hay un único virtual y un no virtual para cada ruta de herencia no virtual a . C++ requiere indicar explícitamente de qué clase padre se invoca la característica que se va a usar, es decir, . C++ no admite la herencia repetida explícita ya que no habría forma de calificar qué superclase usar (es decir, que una clase aparezca más de una vez en una sola lista de derivación [ ]). C++ también permite que se cree una única instancia de la clase múltiple a través del mecanismo de herencia virtual (es decir, y harán referencia al mismo objeto).class B : virtual public AAAAAAWorker::Human.ageclass Dog : public Animal, AnimalWorker::HumanMusician::Human
  • Common Lisp CLOS intenta proporcionar tanto un comportamiento predeterminado razonable como la capacidad de anularlo. Por defecto, en pocas palabras, los métodos se ordenan en D,B,C,A, cuando B se escribe antes que C en la definición de la clase. Se elige el método con las clases de argumento más específicas (D>(B,C)>A)  ; luego en el orden en que se nombran las clases padre en la definición de la subclase (B>C). Sin embargo, el programador puede anular esto, proporcionando un orden de resolución de métodos específico o estableciendo una regla para combinar métodos. Esto se denomina combinación de métodos, que puede controlarse completamente. El protocolo MOP ( protocolo de metaobjetos ) también proporciona medios para modificar la herencia, el despacho dinámico , la instanciación de clases y otros mecanismos internos sin afectar la estabilidad del sistema.
  • Curl solo permite la herencia repetida de clases marcadas explícitamente como compartidas . Las clases compartidas deben definir un constructor secundario para cada constructor regular de la clase. El constructor regular se llama la primera vez que se inicializa el estado de la clase compartida mediante un constructor de subclase, y el constructor secundario se invocará para todas las demás subclases.
  • En Eiffel , las características de los ancestros se eligen explícitamente con las directivas `select` y `rename`. Esto permite que las características de la clase base se compartan entre sus descendientes o que cada uno de ellos tenga una copia independiente de la clase base. Eiffel permite unir o separar explícitamente las características heredadas de las clases ancestro. Eiffel unirá automáticamente las características si tienen el mismo nombre e implementación. El autor de la clase tiene la opción de renombrar las características heredadas para separarlas. La herencia múltiple es frecuente en el desarrollo de Eiffel; la mayoría de las clases efectivas en la biblioteca EiffelBase, ampliamente utilizada para estructuras de datos y algoritmos, por ejemplo, tienen dos o más padres. [ 7 ]
  • Go evita el problema del diamante en tiempo de compilación . Si una estructura Dincrusta dos estructuras By Cque ambas tienen un método F(), satisfaciendo así una interfaz A, el compilador se quejará de un "selector ambiguo" si D.F()se llama a , o si se asigna una instancia de Da una variable de tipo A. Los métodos Bde y Cse pueden llamar explícitamente con D.B.F()o D.C.F().
  • Java 8 introduce métodos predeterminados en las interfaces. Si A, By Cson interfaces, By Cpueden proporcionar cada una una implementación diferente para un método abstracto de A, causando el problema del diamante. Cualquiera de las clases Ddebe reimplementar el método (cuyo cuerpo puede simplemente reenviar la llamada a una de las implementaciones de super), 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 los métodos predeterminados de las interfaces no estaban disponibles. Todas las versiones de Java hasta la versión 26 más reciente no admiten la herencia múltiple directamente: una clase solo puede extender una única clase, pero puede implementar múltiples interfaces con métodos predeterminados.
  • JavaFX Script en la versión 1.2 permite la herencia múltiple mediante el uso de mixins . En caso de conflicto, el compilador prohíbe el uso directo de la variable o función ambigua. Aún se puede acceder a cada miembro heredado convirtiendo el objeto al mixin de interés, por ejemplo (individual as Person).printInfo();.
  • Kotlin permite la herencia múltiple de interfaces; sin embargo, en un escenario de problema de diamante, la clase hija debe sobrescribir el método que causa el conflicto de herencia y especificar qué implementación de la clase padre debe usarse. Por ejemplo: super<ChosenParentInterface>.someMethod()
  • Logtalk admite la herencia múltiple tanto de interfaz como de implementación, lo que permite la declaración de alias de métodos que proporcionan tanto el cambio de nombre como el acceso a métodos que quedarían ocultos por el mecanismo de resolución de conflictos predeterminado.
  • En OCaml , las clases padre se especifican individualmente en el cuerpo de la definición de clase. Los métodos (y atributos) se heredan en el mismo orden, y cada método heredado sobrescribe los métodos existentes. OCaml elige la última definición coincidente de una lista de herencia de clases para determinar qué implementación de método usar en caso de ambigüedad. Para anular el comportamiento predeterminado, basta con calificar la llamada al método con la definición de clase deseada.
  • Perl utiliza la lista de clases de las que heredar como una lista ordenada. El compilador utiliza el primer método que encuentra mediante una búsqueda en profundidad en la lista de superclases o utilizando la linealización C3 de la jerarquía de clases. Varias extensiones proporcionan esquemas alternativos de composición de clases. El orden de herencia afecta a la semántica de la clase. En la ambigüedad anterior, la clase By sus ancestros se comprobarían antes que la clase Cy sus ancestros, por lo que el método en Ase heredaría a través de B. Esto se comparte con Io y Picolisp . En Perl, este comportamiento se puede anular utilizando mrou otros módulos para utilizar la linealización C3 u otros algoritmos. [ 9 ]
  • Python tiene la misma estructura que Perl, pero, a diferencia de Perl, la incluye en la sintaxis del lenguaje. El orden de herencia afecta la semántica de la clase. Python tuvo que lidiar con esto tras la introducción de las clases de nuevo estilo, todas las cuales tienen un ancestro común, 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 varias clases, se mantienen en el orden especificado en la tupla de clases base (sin embargo, en este caso, algunas clases altas en el grafo de herencia pueden preceder a clases más bajas en el grafo [ 10 ] ). Por lo tanto, el orden de resolución de métodos es: D, B, C, A. [ 11 ]
  • Las clases Ruby tienen un único padre, pero pueden heredar de varios módulos; las definiciones de clase Ruby se ejecutan, y la (re)definición de un método oculta cualquier definición previa existente en el momento de la ejecución. En ausencia de metaprogramación en tiempo de ejecución, esto tiene aproximadamente la misma semántica que la resolución en profundidad hacia la derecha.
  • Scala permite la instanciación múltiple de traits , lo que permite la herencia múltiple al agregar una distinción entre la jerarquía de clases y la jerarquía de traits. Una clase solo puede heredar de una única clase, pero puede mezclar tantos traits como desee. Scala resuelve los nombres de los métodos usando una búsqueda en profundidad primero a la derecha de los 'traits' extendidos, antes de eliminar todas las ocurrencias de cada módulo excepto la última en la lista resultante. Por lo tanto, el orden de resolución es: [ D, C, A, B, A], que se reduce a [ D, C, B, A].
  • Tcl permite múltiples clases padre; el orden de especificación en la declaración de clase afecta la resolución de nombres para los miembros que utilizan el algoritmo de linealización C3 . [ 12 ]

Los lenguajes que permiten solo herencia simple , donde una clase solo puede derivar de una clase base, no tienen el problema del diamante. La razón 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 ni 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 punto de la cadena de herencia, sobrescribe 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. Así, la implementación de métodos de herencia simple no presenta el problema del diamante, incluso con la herencia múltiple de interfaces. Con la introducción de la implementación predeterminada para interfaces en Java 8 y C# 8, todavía es posible generar un problema del diamante, aunque esto solo aparecerá como un error de compilación.

Véase también

Referencias

  1. Cargill, TA (Invierno de 1991). "Controversia: El caso en contra de la herencia múltiple en C++". Computing Systems . 4 (1): 69– 82.
  2. Waldo, Jim (Primavera de 1991). "Controversia: El caso de la herencia múltiple en C++". Computing Systems . 4 (2): 157– 171.
  3. Schärli, Nathanael; Ducasse, Stéphane; Nierstrasz, Oscar; Black, Andrew. "Rasgos: Unidades Componibles de Comportamiento" (PDF) . Web.cecs.pdx.edu . Consultado el 21 de octubre de 2016 .
  4. "incr Tcl" . blog.tcl.tk. Consultado el 14 de abril de 2020 .
  5. "Introducción al lenguaje de programación Tcl" . www2.lib.uchicago.edu . Consultado el 14 de abril de 2020 .
  6. Martin, Robert C. (1997-03-09). "Java y C++: una comparación crítica" (PDF) . Objectmentor.com . Archivado del original (PDF) el 24-10-2005 . Recuperado el 21-10-2016 .
  7. "Estándar ECMA-367" . Ecma-international.org . Consultado el 21 de octubre de 2016 .
  8. "Estado de Lambda" . Cr.openjdk.java.net . Consultado el 21 de octubre de 2016 .
  9. "perlobj" . perldoc.perl.org . Consultado el 21 de octubre de 2016 .
  10. Resumen. "El orden de resolución de métodos de Python 2.3" . Python.org . Consultado el 21 de octubre de 2016 .
  11. "Unificación de tipos y clases en Python 2.2" . Python.org . Consultado el 21 de octubre de 2016 .
  12. "Manpage of class" . Tcl.tk. 1999-11-16 . Consultado el 2016-10-21 .
  13. "Interfaces de objetos - Manual" . PHP.net . 4 de julio de 2007. Consultado el 21 de octubre de 2016 .

Lecturas adicionales

  • Stroustrup, Bjarne (1999). Herencia múltiple para C++ . Actas de la Conferencia del Grupo Europeo de Usuarios de Unix de Primavera de 1987.
  • Construcción de software orientado a objetos , segunda edición, por Bertrand Meyer , Prentice Hall, 1997, ISBN 0-13-629155-4
  • Eddy Truyen; Wouter Joosen; Bo Nørregaard; Pierre Verbaeten (2004). "Una generalización y solución al problema del dilema del ancestro común en sistemas de objetos basados ​​en delegación" (PDF) . Actas del Taller de Aspectos Dinámicos de 2004 ( 103–119 ).
  • Ira R. Forman; Scott Danforth (1999). Poniendo las metaclases en práctica . ISBN 0-201-43305-2.
  • Tutorial sobre el uso de la herencia en Eiffel
  • Tutorial sobre el uso eficaz de la herencia múltiple en Python.
  • Una visión general de la herencia en OCaml