¿Por qué la subclasificación es demasiado mala (y, por lo tanto, por qué deberíamos usar prototipos para eliminarla)?

Respuestas:

19

¿Por qué es demasiado malo subclasificar?

"Demasiado" es un llamado al juicio; pero tómalo de mí, es malo. El código con el que trabajo tiene una herencia PROFUNDA y el código es muy difícil de leer, comprender, seguir, rastrear, depurar, etc. etc. Es efectivamente imposible escribir código de prueba para estas cosas.

El patrón prototipo elimina las subclases

¿O la pregunta significa "elimina demasiadas subclases?". Este patrón requiere la clonación de un objeto "plantilla" para evitar subclases, al menos en ese punto. No hay una regla que diga que la "plantilla" no puede ser una subclase.

Favorecer la composición sobre la herencia

Esta idea aquí también incluye delegación y agregación. Seguir esta heurística significa que su software tiende a ser más flexible, más fácil de mantener, ampliar y reutilizar.

Cuando una clase se compone de partes , puede sustituirlas en tiempo de ejecución . Esto tiene un profundo efecto en la capacidad de prueba.

La prueba es más fácil . Puede usar partes falsas (es decir, "simulacros", "dobles" y otras charlas de prueba). La herencia profunda de nuestro código significa que debemos instanciar toda la jerarquía para probar cualquier parte de ella. En nuestro caso, eso no es posible sin ejecutar el código en su entorno real. Por ejemplo, necesitamos una base de datos para crear instancias de objetos comerciales.

Los cambios vienen con efectos secundarios e incertidumbre : cuanto más "base" sea la clase, más extendidos serán los efectos, para bien o para mal. Puede haber cambios deseados que no se atreva a hacer debido a la incertidumbre de los efectos secundarios. O un cambio que es bueno para algún lugar en nuestra cadena de herencia es malo para otro. Esta es ciertamente mi experiencia.

radarbob
fuente
Me interesaría mucho ver algunos ejemplos de cuándo la herencia dificulta las pruebas (especialmente las burlas) y cómo la composición ayuda con eso.
Armand
@alison: un método en una clase derivada que usa otro método de la clase base donde la implementación en la clase base hace que sea prácticamente imposible probar escenarios de error. Si esa hubiera sido la composición, sería más fácil inyectar un objeto que causara el error
Rune FS
@allison: ¿ejemplos? Cuando la base del código es muy grande, cuando las clases base son utilizadas por docenas de clases directamente y cientos por herencia. Debido a que el punto de herencia profunda es la reutilización y, por lo tanto, una clase base se genérica hasta el punto de que la traza de la pila del depurador no puede mostrar qué métodos se están llamando realmente. Finalmente, cuando no hay una inyección de dependencia ya integrada en tal Frankenstein. Una clase de nivel superior puede "ser una" media docena o más de otras cosas que colectivamente "tienen" docenas de objetos internos, cada uno con su propia casa de diversión de herencia.
radarbob
8

Creo que una de las razones por las que esto se ha considerado una mala práctica es que, con el tiempo, cada subclase puede contener atributos y métodos que no tienen sentido para ese objeto a medida que avanza en la cadena (en una jerarquía poco desarrollada). Puede terminar con una clase hinchada que contiene elementos que no tienen sentido para esa clase.

Soy agnóstico sobre esto yo mismo. Parece dogmático decir que es malo. Cualquier cosa es mala cuando se usa mal.

jmq
fuente
2

Dos razones.

  1. Es el rendimiento en tiempo de ejecución. Cada vez que invocas una subclase de una superclase, estás haciendo una llamada a un método, por lo que si has anidado cinco clases e invocas un método en la clase base, estarás haciendo cinco invocaciones de métodos. La mayoría de los compiladores / tiempos de ejecución tratarán de reconocer esto y optimizar las llamadas adicionales, pero solo es seguro hacerlo en casos muy simples.

  2. Es la cordura de tus compañeros programadores. Si anidas cinco clases, tengo que examinar las cuatro clases para padres para establecer que realmente estás llamando a un método en la clase base, se vuelve tedioso. También es posible que tenga que pasar por cinco invocaciones de métodos cuando depure el programa.

James Anderson
fuente
66
-1: Tienes razón sobre el # 2; pero no sobre el # 1. Varias capas de herencia no necesariamente afectarán el rendimiento del tiempo de ejecución.
Jim G.
Preocupado por un par de llamadas a procedimientos adicionales a nivel de código de máquina y utilizando OP y herencia, y le preocupa la cordura de sus compañeros programadores .....
mattnz
@JimG. Puede que no, pero puede. :) También es posible preocuparse por el uso de la memoria: si no reutiliza todas las variables de la base, aún pueden asignarse como parte de la construcción del objeto.
Adam Lear
2

"Demasiado" es un llamado al juicio.

Como con la mayoría de las cosas en programación, la subclasificación no es inherentemente mala cuando tiene sentido. Cuando el modelo de la solución de su problema tiene más sentido como una jerarquía que como una composición de partes, la subclasificación puede ser la opción preferida.

Dicho esto, favorecer la composición sobre la herencia es una buena regla general.

Cuando subclases, las pruebas pueden ser más difíciles (como señaló radarbob ). En una jerarquía profunda, aislar el código problemático es más difícil porque instanciar una subclase requiere traer toda la jerarquía de la superclase y un montón de código adicional. Con un enfoque de composición, puede aislar y probar cada componente de su objeto agregado con pruebas unitarias, objetos simulados, etc.

La subclasificación también puede hacer que exponga detalles de su implementación que quizás no desee. Esto dificulta el control del acceso a la representación subyacente de sus datos y lo vincula a una implementación particular de su modelo de respaldo.

Un ejemplo en el que puedo pensar es java.util.Properties, que hereda de Hashtable. La clase Propiedades solo está destinada a almacenar pares String-String (esto se observa en los Javadocs). Debido a la herencia, los métodos put y putAll de Hashtable se han expuesto a usuarios de Propiedades. Si bien la forma "correcta" de almacenar una propiedad es con setProperty (), no hay absolutamente nada que impida a un usuario llamar a put () y pasar una Cadena y un Objeto.

Básicamente, la semántica de las propiedades está mal definida debido a la herencia. Además, si alguna vez alguien quisiera cambiar el objeto de respaldo para Propiedades, el impacto tendrá un impacto mucho mayor en el código que usa Propiedades que si las Propiedades hubieran adoptado un enfoque más compositivo.

seggy
fuente
2

La herencia puede romper la encapsulación en algunos casos, y si eso sucede, es malo. Leer para más.


En los viejos tiempos sin herencia, una biblioteca publicaba una API y la aplicación que usa utiliza lo que es públicamente visible, y la biblioteca ahora tiene su propia forma de manejar los engranajes internos que siguen cambiando sin romper la aplicación.

A medida que evolucionan las necesidades, la biblioteca puede evolucionar y puede tener más clientes; o puede proporcionar una funcionalidad extendida heredando de su propia clase con otros sabores. Hasta ahora tan bueno.

Sin embargo, lo fundamental es que si una aplicación toma la clase y comienza a subclasificarla, ahora una subclase es en realidad un cliente en lugar de un socio interno. Sin embargo, a diferencia de una forma clara e inequívoca de que se define la API pública, la subclase ahora es mucho más profunda dentro de la biblioteca (acceso a todas las variables privadas, etc.). Todavía no está mal, pero un personaje desagradable puede conectar una API que está demasiado dentro de la aplicación, así como en la biblioteca.

En esencia, aunque este no siempre sea el caso, pero,

Es fácil hacer un mal uso de la herencia para romper la encapsulación

Permitir subclases puede ser incorrecto si ese punto.

Dipan Mehta
fuente
1

Respuesta rápida corta:

Depende de lo que estés haciendo.

Respuesta aburrida extendida:

Un desarrollador puede hacer una aplicación. usando cualquiera de las subclases o plantillas (prototipo). A veces una técnica funciona mejor. A veces, la otra techinque funciona mejor.

Elegir con la técnica funciona mejor, también requiere, considerar si un escenario de "herencia múltiple" está presente. Incluso si el lenguaje de programación solo admite herencia única, plantillas o prototipos.

Nota complementaria:

Algunas respuestas se relacionan con la "herencia múltiple". Pensamiento, no es la pregunta principal, está relacionado con el tema. Hay varias publicaciones similares sobre "herencia múltiple versus composición". Tuve un caso donde mezclo ambos.

umlcat
fuente