Razonar acerca de si la herencia (o cualquier característica individual, realmente) es necesaria o no sin considerar también el resto de la semántica del lenguaje no tiene sentido; Estás discutiendo en el vacío.
Lo que necesita es una filosofía de diseño de lenguaje consistente; El lenguaje debe ser capaz de resolver elegantemente los problemas para los que está diseñado. El modelo para lograr esto puede o no requerir herencia, pero es difícil juzgar esto sin el panorama general.
Si, por ejemplo, su lenguaje tiene funciones de primera clase, aplicación de funciones parciales, tipos de datos polimórficos, variables de tipo y tipos genéricos, prácticamente ha cubierto las mismas bases que tendría con la herencia clásica de OOP, pero utilizando un paradigma diferente.
Si tiene un enlace tardío, una tipificación dinámica, métodos como propiedades, argumentos de funciones flexibles y funciones de primera clase, también cubre los mismos motivos, pero nuevamente, utilizando un paradigma diferente.
(Encontrar ejemplos para los dos paradigmas descritos se deja como ejercicio para el lector).
Por lo tanto, piense en el tipo de semántica que desea, juegue con ellos y vea si son suficientes sin herencia. Si no lo están, puede decidir agregar la herencia a la mezcla o puede decidir que falta algo más.
Sí, es una decisión de diseño perfectamente razonable omitir la herencia.
De hecho, hay muy buenas razones para eliminar la herencia de implementación, ya que puede producir un código extremadamente complejo y difícil de mantener. Incluso iría tan lejos como para considerar la herencia (ya que generalmente se implementa en la mayoría de los lenguajes OOP) como un error.
Clojure, por ejemplo, no proporciona herencia de implementación, prefiriendo proporcionar un conjunto de características ortogonales (protocolos, datos, funciones, macros) que se pueden usar para lograr los mismos resultados, pero mucho más limpiamente.
Aquí hay un video que encontré muy esclarecedor sobre este tema general, donde Rich Hickey identifica las fuentes fundamentales de complejidad en los lenguajes de programación (incluida la herencia) y presenta alternativas para cada uno: simple hecho fácil
fuente
Cuando me topé por primera vez con el hecho de que las clases VB6 no admiten herencia (solo interfaces), realmente me molestó (y aún lo hace).
Sin embargo, la razón por la que es tan malo es que tampoco tenía parámetros de constructor, por lo que no podía hacer una inyección de dependencia (DI) normal. Si tiene DI, entonces eso es más importante que la herencia porque puede seguir el principio de favorecer la composición sobre la herencia. Esa es una mejor manera de reutilizar el código de todos modos.
Sin embargo, ¿no tienes Mixins? Si desea implementar una interfaz y delegar todo el trabajo de esa interfaz a un conjunto de objetos mediante inyección de dependencia, entonces los Mixins son ideales. De lo contrario, debe escribir todo el código repetitivo para delegar todos los métodos y / o propiedades al objeto secundario. Lo hago mucho (gracias a C #) y es una cosa que desearía no tener que hacer.
fuente
Para no estar de acuerdo con otra respuesta: no, descarta funciones cuando son incompatibles con algo que desea más. Java (y otros lenguajes GC'd) descartó la administración de memoria explícita porque quería más seguridad de tipo. Haskell rechazó la mutación porque quería más razonamiento equitativo y tipos elegantes. Incluso C descartó (o declaró ilegal) ciertos tipos de alias y otros comportamientos porque quería más optimizaciones del compilador.
Entonces la pregunta es: ¿Qué quieres más que herencia?
fuente
free
/delete
le da la capacidad de invalidar referencias; a menos que su sistema de tipos pueda rastrear todas las referencias afectadas, eso hace que el idioma sea inseguro. En particular, ni C ni C ++ son de tipo seguro. Es cierto que puede hacer compromisos en uno u otro (por ejemplo, tipos lineales o restricciones de asignación) para que estén de acuerdo. Para ser precisos, debería haber dicho que Java quería seguridad de tipos con un sistema de tipos particular y simple y una asignación sin restricciones más.null
referencias). Prohibirfree
es una restricción adicional bastante arbitraria. En resumen, si un idioma es de tipo seguro depende más de su definición de seguridad de tipo que del idioma.T
referencia se refiere a unonull
o un objeto de una clase que se extiendeT
;null
es feo, pero las operaciones ennull
arrojar una excepción bien definida en lugar de corromper el tipo invariante anterior. Contraste con C ++: después de una llamada adelete
, unT*
puntero puede apuntar a la memoria que ya no contiene unT
objeto. Peor aún, al hacer una asignación de campo utilizando ese puntero en una asignación, puede actualizar un campo de un objeto de una clase diferente por completo si se coloca en una dirección cercana. Eso no es seguridad de tipo según cualquier definición útil del término.No.
Si desea eliminar una característica del lenguaje base, entonces está declarando universalmente que nunca, nunca es necesario (o injustificadamente difícil de implementar, lo que no se aplica aquí). Y "nunca" es una palabra fuerte en ingeniería de software. Necesitaría una justificación muy poderosa para hacer tal declaración.
fuente
Hablando desde una perspectiva estrictamente C ++, la herencia es útil para dos propósitos principales:
Para el punto 1, siempre y cuando tenga alguna forma de compartir código sin tener que disfrutar de demasiadas acrobacias, puede eliminar la herencia. Para el punto 2. Puede seguir el camino de Java e insistir en una interfaz para implementar esta característica.
Los beneficios de eliminar la herencia son
La compensación es en gran medida entre "No te repitas" y Flexibilidad por un lado y evitar problemas en el otro lado. Personalmente, odiaría ver que la herencia pasara de C ++ simplemente porque algún otro desarrollador puede no ser lo suficientemente inteligente como para prever los problemas.
fuente
Puede hacer mucho trabajo muy útil sin herencia de implementación o mixins, pero me pregunto si debería tener algún tipo de herencia de interfaz, es decir, una declaración que diga si un objeto implementa la interfaz A, entonces también necesita la interfaz B (es decir, A es una especialización de B, por lo que hay una relación de tipo). Por otro lado, su objeto resultante solo necesita registrar que implementa ambas interfaces, por lo que no hay demasiada complejidad allí. Todo perfectamente factible.
Sin embargo, la falta de herencia de implementación tiene una desventaja clara: no podrá construir vtables indexadas numéricamente para sus clases y, por lo tanto, tendrá que hacer búsquedas hash para cada llamada a método (o descubrir una forma inteligente de evitarlas). Esto podría ser doloroso si está enrutando incluso valores fundamentales (por ejemplo, números) a través de este mecanismo. ¡Incluso una muy buena implementación de hash puede ser costosa cuando la golpeas varias veces en cada ciclo interno!
fuente