Olor de código: abuso de herencia [duplicado]

49

En general, en la comunidad OO se ha aceptado que uno debería "favorecer la composición sobre la herencia". Por otro lado, la herencia proporciona tanto un polimorfismo como una forma sencilla y tersa de delegar todo a una clase base, a menos que se invalide explícitamente y, por lo tanto, es extremadamente conveniente y útil. La delegación a menudo puede ser (aunque no siempre) detallada y quebradiza.

El signo más obvio y más obvio de abuso de la herencia en la SED es la violación del Principio de sustitución de Liskov . ¿Cuáles son algunos otros signos de que la herencia es la herramienta equivocada para el trabajo aunque parezca conveniente?

    
pregunta dsimcha 16.10.2010 - 19:27

9 respuestas

42

Cuando se hereda solo para obtener funcionalidad, es probable que esté abusando de la herencia. Esto lleva a la creación de un God Object .

La herencia en sí misma no es un problema, siempre que veas una relación real entre las clases (como los ejemplos clásicos, como Dog extiende Animal) y no pongas métodos en la clase principal que no tiene sentido algunos de sus hijos (por ejemplo, agregar un método de Corteza () en la clase Animal no tendría ningún sentido en una clase de peces que se extiende a Animal).

Si una clase necesita funcionalidad, use la composición (¿tal vez inyectar el proveedor de funcionalidad en el constructor?). Si una clase tiene que SER como otra, entonces usa la herencia.

    
respondido por el Fernando 16.10.2010 - 20:19
34

Yo diría, asumiendo el riesgo de ser derribado, que la herencia es un olor de código en sí mismo :)

El problema con la herencia es que se puede usar para dos propósitos ortogonales:

  • interfaz (para polimorfismo)
  • implementación (para reutilización de código)

En primer lugar, tener un solo mecanismo para obtener ambos es lo que lleva al "abuso de la herencia", ya que la mayoría de la gente espera que la herencia se trate de la interfaz, pero se puede usar para obtener la implementación predeterminada incluso por programadores cuidadosos. tan fácil de pasar por alto esto ...)

De hecho, los idiomas modernos como Haskell o Go, han abandonado la herencia para separar ambas preocupaciones.

Volver a la pista:

Una violación del Principio de Liskov es, por lo tanto, la señal más segura, ya que significa que la parte de "interfaz" del contrato no se respeta.

Sin embargo, incluso cuando se respeta la interfaz, es posible que tenga objetos que se heredan de clases básicas "completas" simplemente porque uno de los métodos se consideró útil.

Por lo tanto, el Principio de Liskov en sí mismo no es suficiente, lo que necesita saber es si el polimorfismo es utilizado o no. Si no lo es, entonces no tenía mucho sentido heredar, eso es un abuso.

Resumen:

  • forzar el Liskov Principle
  • compruebe que realmente se usa

Una forma de evitarlo:

Imponiendo una clara separación de preocupaciones:

  • solo se hereda de las interfaces
  • utilizar la composición para delegar la implementación

significa que se necesitan al menos un par de pulsaciones de tecla para cada método y, de repente, las personas comienzan a pensar si es o no tan buena idea reutilizar esta clase "gorda" solo por una pequeña parte.

    
respondido por el Matthieu M. 16.10.2010 - 20:24
13

¿Es realmente "generalmente aceptado"? Es una idea que he escuchado varias veces, pero no es un principio universalmente reconocido. Como acaba de señalar, la herencia y el polimorfismo son las características distintivas de la POO. Sin ellos, acaba de obtener una programación de procedimientos con una sintaxis tonta.

La composición es una herramienta para construir objetos de una determinada manera. La herencia es una herramienta para construir objetos de una manera diferente. No hay nada de malo en ninguno de ellos; solo tiene que saber cómo funcionan ambos y cuándo es apropiado usar cada estilo.

    
respondido por el Mason Wheeler 16.10.2010 - 19:47
5

La fortaleza de la herencia es la reutilización. De interfaz, datos, comportamiento, colaboraciones. Es un patrón útil para transmitir atributos a nuevas clases.

El inconveniente de usar la herencia es la reutilización. La interfaz, los datos y el comportamiento se encapsulan y transmiten en masa, por lo que es una propuesta de todo o nada. Los problemas se hacen especialmente evidentes a medida que las jerarquías se hacen más profundas, a medida que las propiedades que podrían ser útiles en lo abstracto se vuelven menos y comienzan a oscurecer las propiedades a nivel local.

Cada clase que use el objeto de composición tendrá que reimplementar más o menos el mismo código para interactuar con él. Si bien esta duplicación requiere alguna violación de DRY, brinda a los diseñadores una mayor libertad para elegir y elegir las propiedades de una clase más significativas para su dominio. Esto es particularmente ventajoso cuando las clases se vuelven más especializadas.

En general, se debería preferir crear clases a través de la composición y luego convertirlas a la herencia con el grueso de sus propiedades en común, dejando las diferencias como composiciones.

IOW, YAGNI (No vas a necesitar herencia).

    
respondido por el Huperniketes 16.10.2010 - 20:21
3

"Favorecer la composición sobre la herencia" es la más tonta de las afirmaciones. Ambos son elementos esenciales de la POO. Es como decir "Favorecer los martillos sobre las sierras".

Por supuesto, se puede abusar de la herencia, se puede abusar de cualquier característica del idioma, se pueden abusar las declaraciones condicionales.

    
respondido por el Chuck Stephanski 19.03.2011 - 04:47
1

Quizás lo más obvio sea cuando la clase hereditaria termina con una pila de métodos / propiedades que nunca se usan en la interfaz expuesta. En el peor de los casos, donde la clase base tiene miembros abstractos, encontrará implementaciones vacías (o sin sentido) de los miembros abstractos solo para "completar los espacios en blanco", por así decirlo.

Más sutilmente, a veces se ve en un esfuerzo por aumentar la reutilización del código, dos cosas que tienen implementaciones similares se han comprimido en una implementación, a pesar de que esas dos cosas no están relacionadas. Esto tiende a aumentar el acoplamiento de código y desenredarlos cuando resulta que la similitud era solo una coincidencia que puede ser bastante dolorosa a menos que se detecte temprano.

Actualizado con un par de ejemplos: aunque no está directamente relacionado con la programación como tal, creo que el mejor ejemplo para este tipo de cosas es con la localización / internacionalización. En inglés hay una palabra para "sí". En otros idiomas puede haber muchos, muchos más para poder coincidir gramaticalmente con la pregunta. Alguien podría decir, ah, vamos a tener una cadena para "sí" y eso está bien para muchos idiomas. Entonces se dan cuenta de que la cadena "sí" no corresponde realmente con "sí", sino que en realidad el concepto de "respuesta afirmativa a esta pregunta": el hecho de que sea "sí" todo el tiempo es una coincidencia y solo se ahorra tiempo. tener un "sí" se pierde completamente al desenredar el desastre resultante.

He visto un ejemplo particularmente desagradable de esto en nuestra base de código donde lo que en realidad era un padre - > La relación de los hijos en la que el padre y el hijo tenían propiedades con nombres similares se modeló con herencia. Nadie notó esto porque todo parecía funcionar, luego el comportamiento del niño (en realidad base) y el padre (subclase) tenía que ir en diferentes direcciones. Por suerte, esto sucedió exactamente en el momento equivocado cuando se avecinaba una fecha límite: al tratar de modificar el modelo aquí y allá, la complejidad (tanto en términos de lógica como de duplicación de código) se disparó de inmediato. También fue muy difícil razonar / trabajar con, ya que el código simplemente no se relacionaba con la forma en que el negocio pensaba o hablaba sobre los conceptos que estas clases representaban. Al final, tuvimos que morder la bala y hacer una refactorización importante que podría haberse evitado por completo si el chico original no hubiera decidido que un par de propiedades de sondeo similares con valores que parecían ser los mismos representaban el mismo concepto. p>     

respondido por el FinnNk 16.10.2010 - 19:47
1

Si estás subclasificando A en la clase B pero a nadie le importa si B hereda de A o no, entonces esa es la señal de que podrías estar haciendo las cosas mal.

    
respondido por el tia 17.10.2010 - 18:20
0

Hay situaciones en las que la herencia debe favorecerse sobre la composición, y la distinción es mucho más clara que una cuestión de estilo. Parafraseo de las Normas de codificación C ++ de Sutter y Alexandrescu aquí, ya que mi copia está en mi estante de libros en el trabajo en este momento. Lectura muy recomendable, por cierto.

En primer lugar, se desalienta la herencia cuando es innecesaria, ya que crea una relación muy íntima y estrechamente unida entre las clases básicas y derivadas que pueden causar dolores de cabeza en el camino para el mantenimiento. No es solo la opinión de alguien que la sintaxis de la herencia hace que sea más difícil de leer o algo así.

Dicho esto, se recomienda la herencia cuando desea que la clase derivada se reutilice en contextos donde se está utilizando la clase base, sin tener que modificar el código en ese contexto. Por ejemplo, si tiene un código que comienza a parecer if (type1) type1.foo(); else if (type2) type2.foo(); , probablemente tenga un muy buen caso para analizar la herencia.

En resumen, si solo desea reutilizar los métodos de la clase base, use la composición. Si desea usar una clase derivada en el código que actualmente usa una clase base, use la herencia.

    
respondido por el Karl Bielefeldt 19.03.2011 - 05:31
0

Creo que las batallas como la herencia frente a la composición, son realmente solo aspectos de una batalla central más profunda.

Esa pelea es: ¿qué resultará en la menor cantidad de código, para la mayor capacidad de expansión en futuras mejoras?

Es por eso que la pregunta es tan difícil de responder. En un idioma, podría ser que la herencia se coloque más rápidamente que la composición que en otro idioma, o viceversa.

O podría ser el problema específico que está resolviendo en el código y se beneficia más por la conveniencia que por una visión de crecimiento a largo plazo. Eso es tanto un problema de negocios como uno de programación.

Entonces, para volver a la pregunta, la herencia podría estar equivocada si te pone una chaqueta en algún momento en el futuro, o si está creando un código que no necesita estar allí (las clases se heredan de otros para nada). razón, o peor aún, clases básicas que comienzan a tener que hacer muchas pruebas condicionales para niños).

    
respondido por el Kendall Helmstetter Gelner 10.12.2012 - 07:57

Lea otras preguntas en las etiquetas