Clase base frágil
El problema de la clase base frágil es un problema arquitectónico fundamental de los sistemas de programación orientados a objetos donde las clases base (superclases) se consideran "frágiles" porque las modificaciones aparentemente seguras a una clase base, cuando son heredadas por las clases derivadas, pueden hacer que las clases derivadas no funcionen correctamente. El programador no puede determinar si un cambio de clase base es seguro simplemente examinando de forma aislada los métodos de la clase base.
Una posible solución es hacer que las variables de instancia sean privadas para su clase de definición y obligar a las subclases a usar accesores para modificar los estados de las superclases. Un lenguaje también podría hacer que las subclases puedan controlar qué métodos heredados se exponen públicamente. Estos cambios evitan que las subclases dependan de los detalles de implementación de las superclases y permiten que las subclases expongan solo los métodos de superclase que se aplican a ellas mismas.
Otra solución alternativa podría ser tener una interfaz en lugar de una superclase.
El problema de la clase base frágil se ha atribuido a la recursividad abierta (despacho dinámico de métodos en this
), con la sugerencia de que la invocación de métodos en this
por defecto sea recursividad cerrada (envío estático, enlace temprano) en lugar de recursividad abierta (envío dinámico, enlace tardío), solo usando recursividad abierta cuando se solicita específicamente; las llamadas externas (que no usen this
) se enviarían dinámicamente como de costumbre.
Ejemplo de Java
El siguiente ejemplo trivial está escrito en el lenguaje de programación Java y muestra cómo una modificación aparentemente segura de una clase base puede hacer que una subclase heredada no funcione correctamente al ingresar una recurrencia infinita que resultará en un desbordamiento de pila.
clase Super {} privado int contra = 0; vacío inc1() {} contra++; } vacío inc2() {} contra++; }}clase Subsidio extensiones Super {} @Override vacío inc2() {} inc1(); }}
Llamar al método enlazado dinámicamente inc2() en una instancia de Sub aumentará correctamente el campo contador en uno. Sin embargo, si el código de la superclase se cambia de la siguiente manera:
clase Super {} privado int contra = 0; vacío inc1() {} inc2(); } vacío inc2() {} contra++; }}
una llamada al método enlazado dinámicamente inc2() en una instancia de Sub provocará una repetición infinita entre él y el método inc1() de la superclase y eventualmente provocar un desbordamiento de pila. Este problema podría haberse evitado declarando los métodos en la superclase como final, lo que haría imposible que una subclase los sobrescriba. Sin embargo, esto no siempre es deseable o posible. Por lo tanto, es una buena práctica que las superclases eviten cambiar las llamadas a métodos enlazados dinámicamente.
Soluciones
- Objetivo-C tiene categorías y variables de instancia no estructuradas.
- Componente Pascal depreta llamadas de superclase.
- Java, C++ (Desde C++11) y D permiten que la herencia o anular un método de clase se prohíba etiquetando una declaración de una clase o método, respectivamente, con la palabra clave "
final
". En el libro Effective Java, el autor Joshua Bloch escribe (en el tema 17) que los programadores deben "Designar y documentar la herencia o prohibirla". - C# y VB. NET como Java tienen "
sealed
"y"Not Inheritable
"Las palabras clave de la declaración de clase para prohibir la herencia, y requieren una subclase para usar la palabra clave "override
"sobre métodos dominantes, la misma solución adoptada más tarde por Scala. - Scala requiere una subclase para usar la palabra clave "
override
" explícitamente para anular un método de clase padre. En el libro "Programación en Scala, segunda edición", el autor escribe que (con modificaciones aquí) Si no hubiera método f(), la aplicación original del método f() del cliente no podría haber tenido un modificador de anulación. Una vez que agregue el método f() a la segunda versión de su clase de biblioteca, una recompilación del código cliente daría un error compilado en lugar de comportamiento incorrecto. - En Kotlin las clases y métodos son finales por defecto. Para habilitar la herencia de clase, la clase debe ser marcada con la
open
Modificador. Asimismo, un método debe ser marcado comoopen
para permitir la anulación del método. - Julia sólo permite subtipular tipos abstractos y utiliza la composición como alternativa a la herencia. Sin embargo, tiene un envío múltiple.
Contenido relacionado
Algoritmo de la cadena Lempel-Ziv-Markov
Ward Christensen
Nombre clave