¿Por qué no se permite la herencia múltiple en Java o C #?

115

Sé que la herencia múltiple no está permitida en Java y C #. Muchos libros simplemente dicen que no se permite la herencia múltiple. Pero se puede implementar mediante interfaces. No se comenta nada sobre por qué no está permitido. ¿Alguien puede decirme con precisión por qué no está permitido?

Abdulsattar Mohammed
fuente
1
Al igual que señalar que no son marcos por ahí que permiten MI-como el comportamiento en clases de C #. Y, por supuesto, tiene la opción de tener mixins sin estado (usando métodos de extensiones); sin embargo, al ser apátridas no son muy útiles.
Dmitri Nesteruk
1
Que las interfaces tengan algo que ver con la herencia es una ilusión creada por la sintaxis del lenguaje. Se supone que la herencia te hará más rico. Nada de eso para una interfaz, no heredas la sentadilla y te hace significativamente más pobre. Heredó la deuda de juego del tío Harry, tiene más trabajo por hacer para implementar la interfaz.
Hans Passant

Respuestas:

143

La respuesta corta es: porque los diseñadores de lenguajes decidieron no hacerlo.

Básicamente, parecía que los diseñadores de .NET y Java no permitían la herencia múltiple porque razonaban que agregar MI agregaba demasiada complejidad a los lenguajes y proporcionaba muy pocos beneficios .

Para una lectura más divertida y en profundidad, hay algunos artículos disponibles en la web con entrevistas a algunos de los diseñadores del lenguaje. Por ejemplo, para .NET, Chris Brumme (que trabajó en MS en CLR) ha explicado las razones por las que decidieron no:

  1. Los diferentes idiomas en realidad tienen diferentes expectativas sobre cómo funciona MI. Por ejemplo, cómo se resuelven los conflictos y si las bases duplicadas se fusionan o son redundantes. Antes de que podamos implementar MI en CLR, tenemos que hacer una encuesta de todos los lenguajes, descubrir los conceptos comunes y decidir cómo expresarlos de una manera neutral en cuanto al lenguaje. También tendríamos que decidir si MI pertenece al CLS y qué significaría esto para los lenguajes que no quieren este concepto (presumiblemente VB.NET, por ejemplo). Por supuesto, ese es el negocio en el que estamos como un tiempo de ejecución de lenguaje común, pero aún no hemos podido hacerlo para MI.

  2. El número de lugares en los que la MI es verdaderamente apropiada es bastante pequeño. En muchos casos, la herencia de múltiples interfaces puede hacer el trabajo en su lugar. En otros casos, es posible que pueda utilizar la encapsulación y la delegación. Si tuviéramos que agregar una construcción ligeramente diferente, como mixins, ¿sería realmente más poderoso?

  3. La herencia de implementación múltiple inyecta mucha complejidad en la implementación. Esta complejidad afecta el reparto, el diseño, el envío, el acceso al campo, la serialización, las comparaciones de identidad, la verificabilidad, la reflexión, los genéricos y probablemente muchos otros lugares.

Puedes leer el articulo completo aquí.

Para Java, puede leer este artículo :

Las razones para omitir la herencia múltiple del lenguaje Java provienen principalmente del objetivo "simple, orientado a objetos y familiar". Como lenguaje simple, los creadores de Java querían un lenguaje que la mayoría de los desarrolladores pudieran comprender sin una formación extensa. Con ese fin, trabajaron para hacer que el lenguaje fuera lo más similar posible a C ++ (familiar) sin traspasar la complejidad innecesaria de C ++ (simple).

En opinión de los diseñadores, la herencia múltiple causa más problemas y confusión de los que resuelve. Así que cortaron la herencia múltiple del lenguaje (al igual que redujeron la sobrecarga del operador). La amplia experiencia en C ++ de los diseñadores les enseñó que la herencia múltiple simplemente no valía la pena.

Razzie
fuente
10
Buena comparación. Creo que es bastante indicativo de los procesos de pensamiento que subyacen a ambas plataformas.
CurtainDog
97

Herencia múltiple de implementación es lo que no está permitido.

El problema es que el compilador / tiempo de ejecución no puede averiguar qué hacer si tiene una clase Cowboy y una Artist, ambas con implementaciones para el método draw (), y luego intenta crear un nuevo tipo CowboyArtist. ¿Qué sucede cuando llamas al método draw ()? ¿Hay alguien muerto en la calle o tienes una hermosa acuarela?

Creo que se llama el problema de la herencia del doble diamante.

duffymo
fuente
80
Obtienes una hermosa acuarela de alguien muerto en la calle :-)
Dan F
¿Es este el único problema? Creo, no estoy seguro, que C ++ resuelva este problema con la palabra clave virtual, ¿es cierto? No soy bueno en C ++
Abdulsattar Mohammed
2
En C ++ puede especificar cuál de las funciones de la clase base llamar derivada o volver a implementarla usted mismo.
Dmitry Risenberg
1
El diseño moderno tiende a favorecer la composición sobre la herencia en todos los casos, excepto en los más simples. La herencia múltiple nunca habría contado como un caso simple. Con la composición, tienes un control preciso sobre lo que hace tu clase cuando hay problemas de diamantes como este ...
Bill Michell
Simplemente necesitaría una lista de prioridades. Esto se usa en el sistema de objetos de Common Lisp, CLOS. Todas las clases forman una jerarquía y el método que se llama se determina a partir de ahí. Además, CLOS le permite definir diferentes reglas de modo que un CowboyArtist.draw () pueda dibujar primero un Cowboy y luego un Artista. O lo que sea que inventes.
Sebastian Krog
19

Razón: Java es muy popular y fácil de codificar, debido a su simplicidad.

Entonces, lo que los desarrolladores de Java se sienten difíciles y complicados de entender para los programadores, intentaron evitarlo. Uno de esos tipos de propiedad es la herencia múltiple.

  1. Evitaron los punteros
  2. Evitaron la herencia múltiple.

Problema con la herencia múltiple: problema del diamante.

Ejemplo :

  1. Suponga que la clase A se está divirtiendo con un método (). la clase B y la clase C deriva de la clase A.
  2. Y tanto la clase B como la C, anula el método fun ().
  3. Ahora suponga que la clase D hereda tanto la clase B como la C. (solo suposición)
  4. Crear objeto para la clase D.
  5. D d = nuevo D ();
  6. e intente acceder a d.fun (); => ¿llamará diversión de clase B () o diversión de clase C ()?

Ésta es la ambigüedad existente en el problema de los diamantes.

No es imposible resolver este problema, pero crea más confusión y complejidades al programador mientras lo lee. Causa más problemas de los que intenta resolver.

Nota : Pero de cualquier manera, siempre puede implementar la herencia múltiple indirectamente mediante el uso de interfaces.

usuario1923551
fuente
1
"Pero de cualquier manera, siempre se puede implementar la herencia múltiple indirectamente mediante el uso de interfaces". - lo que requiere que el programador vuelva a especificar el cuerpo de los métodos cada vez, una y otra vez.
Kari
13

Porque Java tiene una filosofía de diseño muy diferente a la de C ++. (No voy a hablar de C # aquí).

Al diseñar C ++, Stroustrup quería incluir funciones útiles, independientemente de cómo se pudieran usar incorrectamente. Es posible estropear en grande con herencia múltiple, sobrecarga de operadores, plantillas y varias otras características, pero también es posible hacer algunas cosas muy buenas con ellas.

La filosofía de diseño de Java es enfatizar la seguridad en las construcciones del lenguaje. El resultado es que hay cosas que son mucho más incómodas de hacer, pero puedes estar mucho más seguro de que el código que estás viendo significa lo que crees que hace.

Además, Java fue en gran medida una reacción de C ++ y Smalltalk, los lenguajes OO más conocidos. Hay muchos otros lenguajes OO (Common Lisp fue en realidad el primero en ser estandarizado), con diferentes sistemas OO que manejan mejor MI.

Sin mencionar que es completamente posible hacer MI en Java, usando interfaces, composición y delegación. Es más explícito que en C ++ y, por lo tanto, es más torpe de usar, pero le dará algo que es más probable que comprenda a primera vista.

No hay una respuesta correcta aquí. Hay diferentes respuestas, y cuál es mejor para una situación determinada depende de las aplicaciones y las preferencias individuales.

David Thornley
fuente
12

La principal (aunque no la única) razón por la que las personas se alejan de la MI es el llamado "problema del diamante" que genera ambigüedad en su implementación. Este artículo de wikipedia lo analiza y lo explica mejor que yo. MI también puede conducir a un código más complejo, y muchos diseñadores de OO afirman que no necesita MI, y si lo usa, su modelo probablemente sea incorrecto. No estoy seguro de estar de acuerdo con este último punto, pero mantener las cosas simples siempre es un buen plan.

Steve
fuente
8

En C ++, la herencia múltiple era un gran dolor de cabeza cuando se usaba incorrectamente. Para evitar esos problemas de diseño populares, se forzó la "herencia" de múltiples interfaces en los lenguajes modernos (java, C #).

inazaruk
fuente
8

La herencia múltiple es

  • difícil de entender
  • difícil de depurar (por ejemplo, si mezcla clases de varios marcos que tienen métodos con nombres idénticos en el fondo, pueden ocurrir sinergias bastante inesperadas)
  • fácil de mal uso
  • no es realmente tan útil
  • difícil de implementar, especialmente si desea que se haga de manera correcta y eficiente

Por lo tanto, se puede considerar una buena elección no incluir la herencia múltiple en el lenguaje Java.

mfx
fuente
3
No estoy de acuerdo con todo lo anterior, habiendo trabajado con Common Lisp Object System.
David Thornley
2
Los lenguajes dinámicos no cuentan ;-) En cualquier sistema similar a Lisp, tiene un REPL que facilita la depuración. Además, CLOS (según tengo entendido, nunca lo he usado realmente, solo leí sobre él) es un sistema de metaobjetos, con mucha flexibilidad y una actitud de rodar por su cuenta. Pero considere un lenguaje compilado estáticamente como C ++, donde el compilador genera una búsqueda de métodos diabólicamente compleja usando múltiples vtables (posiblemente superpuestos): en tal implementación, descubrir qué implementación de un método se invocó podría ser una tarea no tan trivial.
mfx
5

Otra razón es que la herencia única hace que la conversión sea trivial, sin emitir instrucciones de ensamblador (aparte de verificar la compatibilidad de los tipos cuando sea necesario). Si tuviera herencia múltiple, necesitaría averiguar en qué parte de la clase secundaria comienza un determinado padre. Por tanto, el rendimiento es sin duda una ventaja (aunque no la única).

Blindy
fuente
4

En los viejos tiempos (años 70), cuando la informática era más ciencia y menos producción en masa, los programadores tenían tiempo para pensar en un buen diseño y una buena implementación y, como resultado, los productos (programas) tenían alta calidad (por ejemplo, diseño TCP / IP e implementación ). Hoy en día, cuando todo el mundo está programando y los gerentes están cambiando las especificaciones antes de las fechas límite, los problemas sutiles como el que se describe en el enlace de wikipedia de la publicación de Steve Haigh son difíciles de rastrear; por lo tanto, la "herencia múltiple" está limitada por el diseño del compilador. Si te gusta, aún puedes usar C ++ ... y tener toda la libertad que quieras :)


fuente
4
... incluida la libertad de dispararse en el pie, varias veces;)
Mario Ortegón
4

Tomo la declaración de que "la herencia múltiple no está permitida en Java" con una pizca de sal.

La herencia múltiple se define cuando un "tipo" hereda de más de un "tipo". Y las interfaces también se clasifican como tipos según su comportamiento. Entonces Java tiene herencia múltiple. Solo que es más seguro.

Rig veda
fuente
6
Pero no heredas de una interfaz, la implementas. Las interfaces no son clases.
Blorgbeard sale el
2
Sí, pero la herencia nunca se definió tan estrechamente, excepto en algunos libros preliminares. Y creo que deberíamos mirar más allá de la gramática. Ambos hacen lo mismo, y las interfaces se "inventaron" solo por esa razón.
Rig Veda
Tome la interfaz Closeable: un método, close (). Suponga que desea extender eso a Openable: tiene un método open () que establece un campo booleano isOpen en verdadero. Intentar abrir un Openable que ya está abierto arroja una Excepción, al igual que intentar cerrar un Openable que no está abierto ... Cientos de genealogías de clases más interesantes podrían querer adoptar dicha funcionalidad ... sin tener que elegir extender cada una tiempo de clase Openable. Si Openable fuera simplemente una interfaz, no podría proporcionar esta funcionalidad. QED: ¡las interfaces no proporcionan herencia!
Mike
4

La carga dinámica de clases dificulta la implementación de la herencia múltiple.

En Java, en realidad, evitaron la complejidad de la herencia múltiple en lugar de utilizar una interfaz y una herencia únicas. La complejidad de la herencia múltiple es muy alta en una situación como la que se explica a continuación

Problema del diamante de herencia múltiple. Tenemos dos clases B y C que heredan de A. Supongamos que B y C anulan un método heredado y proporcionan su propia implementación. Ahora D hereda tanto de B como de C haciendo herencia múltiple. D debería heredar ese método anulado, ¿jvm no puede decidir qué método anulado se utilizará?

En c ++ se utilizan funciones virtuales para manejar y tenemos que hacerlo de forma explícita.

Esto se puede evitar mediante el uso de interfaces, no hay cuerpos de método. No se pueden crear instancias de interfaces; solo se pueden implementar mediante clases o ampliar mediante otras interfaces.

sujith s
fuente
3

En realidad, surgirá una herencia múltiple debido a la complejidad si las clases heredadas tienen la misma función. es decir, el compilador tendrá una confusión sobre cuál debe elegir (problema del diamante). Entonces, en Java, esa complejidad eliminó y dio interfaz para obtener la funcionalidad como la herencia múltiple. Podemos usar la interfaz

Jinu
fuente
2

Java tiene concepto, es decir, polimorfismo. Hay 2 tipos de polimorfismo en java. Hay sobrecarga de métodos y anulación de métodos. Entre ellos, la anulación del método ocurre con la relación de superclase y subclase. Si estamos creando un objeto de una subclase e invocando el método de superclase, y si la subclase se extiende a más de una clase, ¿qué método de superclase debería llamarse?

O, mientras llama al constructor de superclase por super() , ¿a qué se llamará?

Estas decisiones son imposibles con las características actuales de la API de Java. por lo que la herencia múltiple no está permitida en java.

Abhay Kokate
fuente
Fundamentalmente, el sistema de tipos de Java asume que cada instancia de objeto tiene un tipo, la conversión de un objeto a un supertipo siempre funcionará y conservará la referencia, y la conversión de una referencia a un subtipo conservará la referencia si la instancia es de ese subtipo o una subtipo del mismo. Tales suposiciones son útiles y no creo que sea posible permitir la herencia múltiple generalizada de una manera consistente con ellas.
supercat
1

La herencia múltiple no está permitida en Java directamente, pero sí a través de interfaces.

Razón:

Herencia múltiple: introduce más complejidad y ambigüedad.

Interfaces: interfaces son clases completamente abstractas en Java que le brindan una forma uniforme de delinear correctamente la estructura o el funcionamiento interno de su programa desde su interfaz disponible públicamente, con la consecuencia de una mayor cantidad de flexibilidad y código reutilizable, así como más control. sobre cómo crea e interactúa con otras clases.

Más precisamente, son una construcción especial en Java con la característica adicional que le permite realizar una especie de herencia múltiple, es decir, clases que se pueden convertir a más de una clase.

Tomemos un ejemplo simple.

  1. Suponga que hay 2 superclases clases A y B con los mismos nombres de método pero diferentes funcionalidades. A través del siguiente código con la palabra clave (extiende), la herencia múltiple no es posible.

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. Pero a través de interfaces, con la palabra clave (implementa) la herencia múltiple es posible.

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }
Chaitra Deshpande
fuente
0

¿Alguien puede decirme con precisión por qué no está permitido?

Puede encontrar la respuesta en este enlace de documentación

Una de las razones por las que el lenguaje de programación Java no le permite extender más de una clase es para evitar los problemas de herencia múltiple de estado, que es la capacidad de heredar campos de múltiples clases.

Si se permite la herencia múltiple y cuando crea un objeto instanciando esa clase, ese objeto heredará campos de todas las superclases de la clase. Causará dos problemas.

  1. ¿Qué pasa si los métodos o constructores de diferentes superclases instancian el mismo campo?

  2. ¿Qué método o constructor tendrá prioridad?

Aunque ahora se permite la herencia múltiple de estado, aún puede implementar

Herencia múltiple de tipo : capacidad de una clase para implementar más de una interfaz.

Herencia múltiple de implementación (a través de métodos predeterminados en interfaces): capacidad para heredar definiciones de métodos de múltiples clases

Consulte esta pregunta de SE relacionada para obtener información adicional:

Ambigüedad de herencia múltiple con interfaz

Ravindra babu
fuente
0

En C ++, una clase puede heredar (directa o indirectamente) de más de una clase, lo que se conoce como herencia múltiple .

C # y Java, sin embargo, limitan las clases a una herencia única que cada clase hereda de una única clase principal.

La herencia múltiple es una forma útil de crear clases que combinan aspectos de dos jerarquías de clases dispares, algo que ocurre a menudo cuando se utilizan diferentes marcos de clase dentro de una sola aplicación.

Si dos marcos definen sus propias clases base para las excepciones, por ejemplo, puede usar herencia múltiple para crear clases de excepción que se pueden usar con cualquiera de los marcos.

El problema de la herencia múltiple es que puede generar ambigüedad. El ejemplo clásico es cuando una clase hereda de otras dos clases, cada una de las cuales hereda de la misma clase:

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

En este ejemplo, el flagmiembro de datos está definido por class A. Pero class Ddesciende de class B y class C, que ambos derivan de A, por lo que, en esencia, dos copias de flagestán disponibles debido a dos instancias de Aestán en Dla jerarquía de clases 's. ¿Cuál quieres configurar? El compilador se quejará de que la referencia a flagen Des ambigua . Una solución es eliminar la ambigüedad explícita de la referencia:

B::flag = nflag;

Otra solución es declarar B y C como virtual base classes , lo que significa que solo puede existir una copia de A en la jerarquía, eliminando cualquier ambigüedad.

Existen otras complejidades con la herencia múltiple, como el orden en el que se inicializan las clases base cuando se construye un objeto derivado, o la forma en que los miembros pueden ocultarse inadvertidamente de las clases derivadas. Para evitar estas complejidades, algunos lenguajes se limitan al modelo de herencia única más simple.

Aunque esto simplifica considerablemente la herencia, también limita su utilidad porque solo las clases con un ancestro común pueden compartir comportamientos. Las interfaces mitigan un poco esta restricción al permitir que las clases en diferentes jerarquías expongan interfaces comunes incluso si no están implementadas compartiendo código.

cero frío
fuente
-1

Imagina este ejemplo: tengo una clase Shape1

Tiene CalcualteAreamétodo:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

Hay otra clase Shape2que también tiene el mismo método.

Class Shape2
{

 public void CalculateArea()

     {

     }
}

Ahora tengo una clase secundaria Circle, deriva de Shape1 y Shape2;

public class Circle: Shape1, Shape2
{
}

Ahora, cuando creo un objeto para Circle y llamo al método, el sistema no sabe qué método de área de cálculo se llamará. Ambos tienen las mismas firmas. Entonces el compilador se confundirá. Por eso no se permiten herencias múltiples.

Pero puede haber varias interfaces porque las interfaces no tienen una definición de método. Incluso ambas interfaces tienen el mismo método, ambas no tienen ninguna implementación y siempre se ejecutará el método en la clase secundaria.

Ing. Muhammad Enayet Abdullah
fuente
El diseñador de lenguaje puede realizar una llamada explícita al método como lo hacen en Interface. ¡No existe tal punto! Otra razón es qué hay de la clase abstracta con métodos abstractos ahora, ¿cómo lo considera?
Nomi Ali