¿Por qué evitar la herencia Java "se extiende"


40

Jame Gosling dijo

"Debe evitar la herencia de implementación siempre que sea posible".

y en su lugar, use la herencia de la interfaz.

¿Pero por qué? ¿Cómo podemos evitar heredar la estructura de un objeto usando la palabra clave "extend", y al mismo tiempo hacer que nuestro código esté orientado a objetos?

¿Podría alguien dar un ejemplo orientado a objetos que ilustre este concepto en un escenario como "ordenar un libro en una librería?"



El problema es que solo puedes extender una clase.

Respuestas:


38

Gosling no dijo que evitara el uso de palabras clave extendidas ... solo dijo que no usara la herencia como un medio para lograr la reutilización del código.

La herencia no es mala per se y es una herramienta muy poderosa (esencial) para usar en la creación de estructuras OO. Sin embargo, cuando no se usa correctamente (es decir, cuando se usa para algo más que crear estructuras de Objetos) crea un código que está muy estrechamente acoplado y es muy difícil de mantener.

Debido a que la herencia se debe usar para crear estructuras polimórficas, se puede hacer con la misma facilidad con las interfaces, de ahí la declaración de usar interfaces en lugar de clases al diseñar sus estructuras de objetos. Si se encuentra usando extensiones como un medio para evitar copiar y pegar código de un objeto a otro, tal vez lo esté usando mal y sería mejor servirlo con una clase especializada para manejar esta funcionalidad y compartir ese código a través de la composición.

Lea sobre el Principio de sustitución de Liskov y entiéndalo. Siempre que esté a punto de extender una clase (o una interfaz para el caso) pregúntese si está violando el principio, en caso afirmativo, es muy probable que (ab) use la herencia de formas no naturales. Es fácil de hacer y, a menudo, parece lo correcto, pero hará que su código sea más difícil de mantener, probar y evolucionar.

--- Editar Esta respuesta es un argumento bastante bueno para responder la pregunta en términos más generales.


3
(Implementación) la herencia no es esencial para crear estructuras OO. Puede tener un lenguaje OO sin él.
Andres F.

Podría dar un ejemplo ? Nunca he visto OO sin herencia.
Newtopian

1
El problema es que no hay una definición aceptada de lo que es OOP. (Por otro lado, incluso Alan Kay llegó a lamentar la interpretación común de su definición, con su énfasis actual en "objetos" en lugar de "mensajes"). Aquí hay un intento de respuesta a su pregunta . Estoy de acuerdo en que es raro ver POO sin herencia; por lo tanto, mi argumento fue simplemente que no es esencial ;)
Andres F.

9

En los primeros días de la programación orientada a objetos, la herencia de implementación fue el martillo de oro de la reutilización de código. Si había una funcionalidad que necesitabas en alguna clase, lo que había que hacer era heredar públicamente de esa clase (estos eran los días en que C ++ era más frecuente que Java), y obtienes esa funcionalidad "gratis".

Excepto que no era realmente gratis. El resultado fueron jerarquías de herencia extremadamente complicadas que eran casi imposibles de entender, incluidos los árboles de herencia en forma de diamante que eran difíciles de manejar. Esta es parte de la razón por la cual los diseñadores de Java optaron por no admitir la herencia múltiple de clases.

El consejo de Gosling (y otras declaraciones) como si fuera un medicamento fuerte para una enfermedad bastante grave, y es posible que algunos bebés se echen con el agua del baño. No hay nada de malo en usar la herencia para modelar una relación entre clases que realmente lo es is-a. Pero si solo está buscando utilizar una funcionalidad de otra clase, será mejor que investigue otras opciones primero.


6

La herencia ha acumulado una reputación bastante aterradora últimamente. Una razón para evitarlo va junto con el principio de diseño de "composición sobre herencia", que básicamente alienta a las personas a no usar la herencia donde esa no sea la verdadera relación, solo para guardar algo de escritura de código. Este tipo de herencia puede causar algunos errores difíciles de encontrar y difíciles de corregir. La razón por la que su amigo probablemente sugirió usar una interfaz en su lugar es porque las interfaces proporcionan una solución más flexible y fácil de mantener para las API distribuidas. Con mayor frecuencia permiten que se realicen cambios en una API sin romper el código del usuario, donde la exposición de clases a menudo sí rompe el código del usuario. El mejor ejemplo de esto en .Net es la diferencia de uso entre List e IList.


5

Porque solo puede extender una clase, mientras que puede implementar muchas interfaces.

Una vez escuché que la herencia define lo que eres, pero las interfaces definen un papel que puedes jugar.

Las interfaces también permiten un mejor uso del polimorfismo.

Eso podría ser parte de la razón.


¿Por qué el voto negativo?
Mahmoud Hossam

66
La respuesta pone el carro delante del caballo. Java solo le permite extender una clase debido al principio articulado Gosling (diseñador de Java). Es como decir que los niños deben usar cascos mientras andan en bicicleta porque esa es la ley. Eso es cierto, pero tanto la ley como el consejo provienen de evitar lesiones en la cabeza.
JohnMcG

@JohnMcG No estoy explicando la elección de diseño que hizo Gosling, simplemente estoy dando consejos a un codificador, los codificadores generalmente no se molestan con el diseño del lenguaje.
Mahmoud Hossam

1

También me han enseñado esto y prefiero las interfaces siempre que sea posible (por supuesto, todavía uso la herencia donde tiene sentido).

Una cosa que creo que hace es desacoplar su código de implementaciones específicas. Digamos que tengo una clase llamada ConsoleWriter y que se pasa a un método para escribir algo y lo escribe en la consola. Ahora digamos que quiero cambiar a imprimir en una ventana GUI. Bueno, ahora tengo que modificar el método o escribir uno nuevo que tome GUIWriter como parámetro. Si comencé definiendo una interfaz IWriter y el método tomara un IWriter, podría comenzar con ConsoleWriter (que implementaría la interfaz IWriter) y luego escribir una nueva clase llamada GUIWriter (que también implementa la interfaz IWriter) y luego solo tendría que cambiar la clase que se pasa.

Otra cosa (que es cierto para C #, no estoy seguro acerca de Java) es que solo puede extender 1 clase pero implementar muchas interfaces. Digamos que tuve una clase llamada Profesor, Profesor de matemáticas e Profesor de historia. Ahora MathTeacher e HistoryTeacher se extienden desde Teacher, pero ¿qué pasa si queremos una clase que represente a alguien que sea MathTeacher e HistoryTeacher? Puede ser bastante complicado cuando intenta heredar de varias clases cuando solo puede hacerlo de una en una (hay una forma, pero no son exactamente óptimas). Con las interfaces puede tener 2 interfaces llamadas IMathTeacher e IHistoryTeacher y luego tener una clase que se extienda desde Teacher e implemente esas 2 interfaces.

Una desventaja que con el uso de interfaces es que a veces veo personas duplicando el código (ya que hay que crear la implementación para cada clase, implementar la interfaz), sin embargo, existe una solución limpia para este problema, por ejemplo, el uso de cosas como delegados (no estoy seguro cuál es el equivalente de Java a eso).

Creo que la razón más importante para usar interfaces en lugar de herencias es el desacoplamiento del código de implementación, pero no piense que la herencia es mala, ya que sigue siendo muy útil.


2
"solo puede extender 1 clase pero implementar muchas interfaces" Eso es cierto tanto para Java como para C #.
FrustratedWithFormsDesigner

Una posible solución al problema de duplicación de código es crear una clase abstracta que implemente la interfaz y la extienda. Usar con cuidado, sin embargo; Solo he visto algunos casos que tiene sentido.
Michael K

0

Si hereda de una clase, toma una dependencia no solo de esa clase sino que también tiene que cumplir con los contratos implícitos que hayan sido creados por los clientes de esa clase. El tío Bob ofrece un gran ejemplo de cómo es fácil violar sutilmente el Principio de sustitución de Liskov usando el dilema básico Rectángulo cuadrado . Las interfaces, dado que no tienen implementación, tampoco tienen contratos ocultos.


2
Un punto crítico: el problema Circle-ellipse seguirá ocurriendo incluso si solo se usan interfaces puras, si los objetos deben ser mutables.
rwong
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.