¿Existe una razón de uso diferente para las clases / interfaces abstractas en C ++ y Java

13

Según Herb Sutter, uno debería preferir interfaces abstractas (todas las funciones virtuales puras) a clases abstractas en C ++ para desacoplar la implementación lo más posible. Si bien personalmente considero que esta regla es muy útil, recientemente me uní a un equipo con muchos programadores de Java y, en el código de Java, esta guía no parece existir. Las funciones y sus implementaciones se encuentran muy frecuentemente en clases abstractas. Entonces, ¿entendí mal a Herb Sutter incluso para C ++ o hay una diferencia general en el uso de funciones abstractas en C ++ en comparación con Java. ¿Las clases abstractas con código de implementación son más sensibles en Java que en C ++ y, en caso afirmativo, por qué?

Martín
fuente
1
Tenía algunas dudas y finalmente lo puse aquí porque podría deberse a algunos principios de diseño que me falta sobre el java oo. De manera que no se trata de un consejo general, pero más sobre el derecho y el mal uso de la lengua
Martin
Las interfaces están destinadas a ser puramente virtuales. La idea de las clases abstractas es que se implementan parcialmente, y depende de la implementación llenar los huecos sin repetir código innecesariamente (por ejemplo, por qué escribir (byte) y escribir (int) en cada subclase cuando puede tener el llamada de clase abstracta write (byte) from write (int))
1
Posiblemente relacionado: stackoverflow.com/q/1231985/484230 da una razón por la que prefieren las clases abstractas en java. Para C ++, esta razón no parece ser cierta debido a la existencia de funciones gratuitas que pueden agregar funcionalidad en el nivel de interfaz
Martin
1
Creo que la regla de oro es "hacer que las clases no abstractas sean abstractas", pero eso no establece ningún requisito "solo puro" o "vacío".
Kerrek SB
1
Si funciona para ti, funciona para ti. Realmente no veo por qué la gente entra en pánico una vez que su código ya no se adhiere a las últimas opiniones.
James

Respuestas:

5

OOP tiene composición y sustitución.

C ++ tiene herencia múltiple, especialización de plantilla, incrustación y semántica de valor / movimiento / puntero.

Java tiene herencia única e interfaces, incrustación y semántica de referencia.

La manera común en que la escuela OOP usa estos idiomas es emplear la herencia para la sustitución de objetos y la inclusión para la composición. Pero también necesita un ancestro común y una forma de ejecutar en tiempo de ejecución (en C ++ se llama dynamic_cast, en Java solo se le pide una interfaz a otra).

Java hace todo esto por su propia java.lang.Objectjerarquía enraizada. C ++ no tiene una raíz común predefinida, por lo que al menos debería definirla para llegar a una misma "imagen" (pero esto limita algunas posibilidades de C ++ ...).

Después de eso, la posibilidad de tener polimorfismo en tiempo de compilación (piense en CRTP) y valor semántico puede ofrecer también otras alternativas a la forma en que el concepto de "objeto OOP" se puede portar a un programa C ++.

Incluso puede imaginar la herejía de utilizar la conversión implícita y la incorporación para gestionar la sustitución y la herencia privada para gestionar la composición, de hecho invirtiendo el paradigma tradicional de la escuela. (Por supuesto, de esta manera es 20 años más joven que la otra, así que no esperes un amplio apoyo de la comunidad para hacerlo)

O puede imaginar una base común virtual para todas las clases, desde la interfaz de formulario (sin implementación) hasta las clases finales (completamente implementadas) pasando por interfaces parcialmente implementadas y grupos de interfaces uniformes, utilizando el "dominio" como envío de la interfaz a las implementaciones a través de un "multi apilado -parallelogram "esquema de herencia.

Comparar OOP con java y C ++ suponiendo que solo haya una y única forma de OOP es limitar las capacidades de ambos lenguajes.

Obligar a C ++ a adherirse estrictamente a los modismos de codificación de Java es desnaturalizar C ++ como forzar a Java a comportarse como un lenguaje similar a C ++ es desnaturalizar Java.

No es una cuestión de "sensibilidad" sino de diferentes "mecanismos de agregación" que los dos idiomas tienen y una forma diferente de combinarlos que hace que un idioma sea más rentable en un idioma que en el otro y viceversa.

Emilio Garavaglia
fuente
1
Creo que esta respuesta es muy interesante, ya que describe sucintamente las características del lenguaje como una herramienta para oo y principios de diseño solo como una ayuda y no como una doctrina. Sin embargo, no necesita una raíz común si desea hacer oo en c ++. Esto es simplemente incorrecto, también por el hecho de que tiene operadores y plantillas (que son una alternativa muy poderosa al diseño de árbol principal de Java como también señaló). Aparte de eso, sus puntos son los más valiosos en todas las respuestas
Martin
1
@ Martin: En "sentido técnico" tiene razón, pero si necesita polimorfismo en tiempo de ejecución (porque el tipo real de los objetos instanciados depende de la entrada del programa) una "raíz" ('a' es un artículo, no un atajo para " one and only ") es lo que hace que todos los objetos sean" primos ", y la jerarquía sea ejecutable en tiempo de ejecución. Las diferentes raíces originan diferentes ancestros no relacionados entre sí. Si esto es "bueno" o "malo" es una cuestión de contexto, no de idioma.
Emilio Garavaglia
Es verdad. Pensé que se refería a proporcionar artificialmente una raíz general para un programa completo de c ++ y lo vi como un defecto que no está presente, en comparación con Java. Pero después de su edición, deja el punto bastante claro. Gracias de nuevo
Martin
12

El principio es válido para ambos idiomas, pero no está haciendo una comparación justa. Debe comparar las clases abstractas puras de C ++ con las interfaces Java.

Incluso en C ++, puede tener clases abstractas que tengan implementadas algunas de las funciones, pero que se deriven de una clase abstracta pura (sin implementaciones). En Java, tendría las mismas clases abstractas (con algunas implementaciones), que pueden derivarse de las interfaces (sin implementaciones).

Luchian Grigore
fuente
Entonces, ¿cuándo preferiría una clase abstracta sobre una clase de interfaz en c ++. Siempre opté por la interfaz más las funciones que no son miembros en c ++.
Martin
1
@ Martin que depende del diseño. Básicamente, siempre prefiero una interfaz. Pero las reglas " siempre " tienen excepciones ...
Luchian Grigore
Es cierto, pero en el Código Java veo clases abstractas que representan en gran medida a la mayoría. ¿Podría esto ser debido al hecho de que las funciones libres que trabajan en interfaces no son posibles en Java?
Martin
3
Las funciones libres de @Martin no son posibles en Java, por lo que podría ser una razón, sí. ¡Buen lugar! Respondió tu propia pregunta! Puede agregar una respuesta usted mismo, creo que eso es todo.
Luchian Grigore
4

En general, los mismos principios OO son válidos para Java y C ++. Sin embargo, una gran diferencia es que C ++ admite herencia múltiple, mientras que en Java solo se puede heredar de una clase. Esta es la razón principal por la que Java tiene interfaces, creo, para complementar la falta de herencia múltiple y probablemente para restringir lo que puede hacer con ella (ya que hay muchas críticas sobre el abuso de la herencia múltiple). Entonces, probablemente en la mente de los programadores de Java, hay una distinción más fuerte entre clases abstractas e interfaces. Las clases abstractas se usan para compartir y heredar el comportamiento, mientras que las interfaces simplemente se usan para agregar funcionalidad adicional. Recuerde, en Java puede heredar de una sola clase, pero puede tener muchas interfaces. Sin embargo, en C ++, las clases abstractas puras (es decir, una "interfaz C ++") son se usa para compartir y heredar comportamientos a diferencia del propósito de una interfaz Java (aunque todavía se requiere que implemente las funciones), por lo tanto, el uso es diferente de las interfaces Java.

Jesse Good
fuente
0

A veces tiene sentido tener alguna implementación predeterminada. Por ejemplo, un método genérico PrintError (string msg) que es aplicable a todas las subclases.

virtual PrintError(string msg) { cout << msg; }

Todavía puede anularse si es realmente necesario, pero puede ahorrarle problemas al cliente permitiéndole simplemente llamar a la versión genérica.

Ciencia ficción
fuente