Casos de uso de herencia múltiple

15

Java omite la herencia múltiple debido a que obvia el objetivo de diseño de mantener el lenguaje simple .

Me pregunto si Java (con su ecosistema) es realmente "simple". Python no es complejo y tiene herencia múltiple. Entonces, sin ser demasiado subjetivo, mi pregunta es ...

¿Cuáles son los patrones de problemas típicos que se benefician de un código diseñado para hacer un uso intensivo de la herencia múltiple?

codificador de árboles
fuente
8
Java omite muchísimas cosas perfectamente buenas por muy poca razón. No esperaría una justificación decente para MI.
DeadMG
2
La herencia múltiple de Python es definitivamente un territorio de ser dragones . El hecho de que utilice una resolución de nombre de profundidad a izquierda y derecha tiene problemas importantes tanto para la mantenibilidad como para la comprensión. Si bien puede ser útil en jerarquías de clase poco profundas, puede ser increíblemente contra-intuitivo en las más profundas.
Mark Booth
Creo que la razón por la que Java no contiene herencia múltiple es que los desarrolladores de Java querían que su lenguaje fuera fácil de aprender. La herencia múltiple, aunque increíblemente poderosa en algunos casos, es difícil de entender, y aún más difícil de usar con buenos resultados; no es lo que le gustaría enfrentar a un estudiante de primer año de programación. Quiero decir: ¿Cómo explicas la herencia virtual a alguien que está luchando con el concepto de herencia en sí? Y, dado que la herencia múltiple tampoco es exactamente trivial en el lado de los implementadores, es probable que el desarrollador de Java omita que es una ganancia para todos.
cmaster - reinstalar a monica el
Java está nominalmente escrito. Python no lo es. Esto hace que la herencia múltiple sea mucho más fácil de implementar y comprender en Python.
Jules

Respuestas:

11

Pros:

  1. A veces permite un modelado más obvio de un problema que otras formas de modelarlo.
  2. Si los diferentes padres tienen un propósito ortogonal, puede permitir algún tipo de composición

Contras :

  1. Si los diferentes padres no tienen un propósito ortogonal, hace que el tipo sea difícil de entender.
  2. No es fácil entender cómo se implementa en un idioma (cualquier idioma).

En C ++, un buen ejemplo de herencia múltiple utilizada para componer características ortogonales es cuando usa CRTP para, por ejemplo, configurar un sistema de componentes para un juego.

Empecé a escribir un ejemplo, pero creo que vale la pena mirar un ejemplo del mundo real. Algún código de Ogre3D usa herencia múltiple de una manera agradable y muy intuitiva. Por ejemplo, la clase Mesh hereda de Resources y AnimationContainer. Los recursos exponen la interfaz común a todos los recursos y AnimationContainer expone la interfaz específica para manipular un conjunto de animaciones. No están relacionados, por lo que es fácil pensar en una malla como un recurso que, además, puede contener un conjunto de animaciones. Se siente natural, ¿no?

Puede ver otros ejemplos en esta biblioteca , como la forma en que la asignación de memoria se gestiona de forma multada haciendo que las clases hereden de variantes de una sobrecarga de clases CRTP nuevas y eliminadas.

Como se dijo, los principales problemas con la herencia múltiple surgen de mezclar conceptos relacionados. Hace que el lenguaje tenga que establecer implementaciones complejas (vea la forma en que C ++ permite jugar con el problema del diamante ...) y el usuario no está seguro de lo que sucede en esa implementación. Por ejemplo, lea este artículo que explica cómo se implementa en C ++ .

Eliminarlo del idioma ayuda a evitar que las personas que no saben cómo se informa el idioma hagan las cosas malas. Pero obliga a pensar de una manera que, a veces, no se siente natural, incluso si se trata de casos extremos, sucede con más frecuencia de lo que piensas.

Klaim
fuente
Realmente agradecería que adornase su respuesta con un problema de ejemplo, eso
aclarará
Ok, déjame intentar agregar algo.
Klaim
Ogre3D no es un lugar en el que buscaría inspiración para el diseño: ¿has visto su infección Singleton?
DeadMG
Primero, el heredero singleton no es realmente un singleton, la construcción y la destrucción son explícitas. A continuación, Ogre es una capa sobre un sistema de hardware (o el controlador gráfico si lo prefiere). Eso significa que solo debe haber una representación única para las interfaces del sistema (como Root u otras). Pueden eliminar algunos singleton, pero ese no es el punto aquí. He evitado voluntariamente señalar esto para evitar tener una discusión troll, así que por favor, mira los ejemplos que he señalado. Su uso de Singleton puede no ser perfecto, pero es claramente útil en la práctica (pero solo para su tipo de sistema, no todo).
Klaim
4

Hay un concepto llamado mixins que se usa mucho en lenguajes más dinámicos. La herencia múltiple es una forma en la que un lenguaje puede admitir mixins. Los mixins se usan generalmente como una forma para que una clase acumule diferentes piezas de funcionalidad. Sin herencia múltiple, debe usar la agregación / delegación para obtener un comportamiento de tipo mixin con una clase, que es un poco más sintáctica.

RationalGeek
fuente
+1 esta es una buena razón para tener herencia múltiple. Los mixins tienen connotaciones adicionales ("esta clase no se debe usar como un elemento independiente")
cenizas999
2

Creo que la elección se basa principalmente en cuestiones debidas al problema del diamante .

Además, a menudo es posible eludir el uso de la herencia múltiple por delegación u otros medios.

No estoy seguro del significado de tu última pregunta. Pero si es "¿en qué casos es útil la herencia múltiple?", Entonces, en todos los casos en que le gustaría tener un objeto A que tenga funcionalidades de los objetos B y C, básicamente.

dagnelies
fuente
2

No profundizaré mucho aquí, pero seguramente puedes entender la herencia múltiple en Python a través del siguiente enlace http://docs.python.org/release/1.5.1p1/tut/multiple.html :

La única regla necesaria para explicar la semántica es la regla de resolución utilizada para las referencias de atributos de clase. Esto es primero en profundidad, de izquierda a derecha. Por lo tanto, si no se encuentra un atributo en DerivedClassName, se busca en Base1, luego (recursivamente) en las clases base de Base1, y solo si no se encuentra allí, se busca en Base2, y así sucesivamente.

...

Está claro que el uso indiscriminado de la herencia múltiple es una pesadilla de mantenimiento, dada la dependencia en Python de las convenciones para evitar conflictos de nombres accidentales. Un problema bien conocido con herencia múltiple es una clase derivada de dos clases que tienen una clase base común. Si bien es bastante fácil averiguar qué sucede en este caso (la instancia tendrá una sola copia de `` variables de instancia '' o atributos de datos utilizados por la clase base común), no está claro que esta semántica sea de alguna manera útil.

Este es solo un pequeño párrafo, pero lo suficientemente grande como para despejar las dudas, supongo.

Pankaj Upadhyay
fuente
1

Un lugar donde la herencia múltiple sería útil es una situación en la que una clase implementa varias interfaces, pero le gustaría tener alguna funcionalidad predeterminada incorporada en cada interfaz. Esto es útil si la mayoría de las clases que implementan alguna interfaz quieren hacer algo de la misma manera, pero ocasionalmente necesitas hacer algo diferente. Puede tener cada clase con la misma implementación, pero tiene más sentido empujarla hacia arriba en una ubicación.

KeithB
fuente
1
¿Eso requeriría una herencia múltiple generalizada, o simplemente un medio por el cual una interfaz puede especificar comportamientos predeterminados para métodos no implementados? Si las interfaces solo pudieran especificar implementaciones predeterminadas para los métodos que ellos mismos implementan (a diferencia de las que heredan de otras interfaces), esta característica evitaría por completo los problemas de doble diamante que dificultan la herencia múltiple.
supercat
1

¿Cuáles son los patrones de problemas típicos que se benefician de un código diseñado para hacer un uso intensivo de la herencia múltiple?

Este es solo un ejemplo, pero me parece invaluable para mejorar la seguridad y mitigar las tentaciones de aplicar cambios en cascada a través de las personas que llaman o las subclases.

Donde he encontrado que la herencia múltiple es increíblemente útil incluso para las interfaces más abstractas y sin estado es el idioma de interfaz no virtual (NVI) en C ++.

Ni siquiera son clases base realmente abstractas, sino interfaces que solo tienen un poco de implementación para hacer cumplir los aspectos universales de sus contratos, ya que en realidad no están reduciendo la generalidad del contrato, sino más bien haciéndolo cumplir mejor .

Ejemplo simple (algunos podrían comprobar que un identificador de archivo pasado está abierto o algo así):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

En este caso, quizás fsea ​​llamado por mil lugares en la base de código, mientras que f_imples anulado por cien subclases.

Sería difícil hacer este tipo de verificación de seguridad en los 1000 lugares que llaman fo en los 100 lugares que anulan f_impl.

Al hacer que este punto de entrada a la funcionalidad no sea virtual, me da un lugar central para realizar esta verificación. Y esta verificación no está reduciendo la abstracción en lo más mínimo, ya que simplemente está afirmando una condición previa requerida para llamar a esta función. En cierto sentido, podría decirse que fortalece el contrato proporcionado por la interfaz y alivia la carga de verificar la xentrada para asegurarse de que se ajuste a las condiciones previas válidas en los 100 lugares que la anulan.

Es algo que desearía que todos los lenguajes tuvieran, y también deseaba, incluso en C ++, que fuera un concepto un poco más nativo (por ejemplo: no nos obliga a definir una función separada para anular).

Esto es extremadamente útil si no lo hizo assertpor adelantado, y se dio cuenta de que lo necesitaba más tarde cuando algunos lugares aleatorios en la base de código encontraban valores negativos que se les pasaban f.


fuente
0

Primero: múltiples copias de la clase base (un problema de C ++) y un acoplamiento estrecho entre las clases base y derivadas.

Segundo: herencia múltiple de interfaces abstractas

cuant_dev
fuente
¿Estás sugiriendo que no es útil en ningún contexto? ¿Y que todo se puede diseñar / codificar convenientemente sin él? También por favor explique sobre el segundo punto.
treecoder