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 funcionen mal . 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 definitoria y forzar a las subclases a usar descriptores de acceso para modificar estados de superclase. 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 se basen en los detalles de implementación de las superclases y permiten que las subclases expongan solo los métodos de superclase que se les aplican.
Otra solución alternativa podría ser tener una interfaz en lugar de una superclase.
El frágil problema de la clase base se ha atribuido a la recursividad abierta (envío dinámico de métodos en this
), con la sugerencia de que invocar métodos por this
defecto a la recursividad cerrada (envío estático, enlace temprano) en lugar de la recursividad abierta (envío dinámico, enlace tardío), solo usar la recursividad abierta cuando se solicita específicamente; las llamadas externas (sin usar this
) se enviarían dinámicamente como de costumbre. [1] [2]
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 causar un mal funcionamiento de una subclase heredada al ingresar una recursividad infinita que resultará en un desbordamiento de pila .
clase Super { contador int privado = 0 ; vacío inc1 () { contador ++ ; } vacío inc2 () { contador ++ ; }}class Sub extiende Super { @Override void inc2 () { inc1 (); }}
Llamar al método inc2 () enlazado dinámicamente en una instancia de Sub aumentará correctamente el contador de campo en uno. Sin embargo, si el código de la superclase se cambia de la siguiente manera:
clase Super { contador int privado = 0 ; vacío inc1 () { inc2 (); } vacío inc2 () { contador ++ ; } }
una llamada al método inc2 () enlazado dinámicamente en una instancia de Sub causará una recursividad infinita entre él y el método inc1 () de la superclase y eventualmente causará un desbordamiento de pila. Este problema podría haberse evitado declarando los métodos de la superclase como finales , lo que haría imposible que una subclase los invalidara. 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 vinculados dinámicamente.
Soluciones
- Objective-C tiene categorías y variables de instancia no frágiles .
- El componente Pascal desaprueba las llamadas de superclase .
- Java , C ++ (desde C ++ 11) y D permiten prohibir la herencia o la invalidación de un método de clase 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 ítem 17) que los programadores deben "Diseñar y documentar la herencia o prohibirla".
- C # y VB.NET como Java tienen palabras clave de declaración de clase " selladas " y " No heredables" para prohibir la herencia, y requieren una subclase para usar la palabra clave " override " en los métodos de override , [3] la misma solución adoptada posteriormente por Scala.
- Scala requiere que una subclase utilice la palabra clave " anular " explícitamente para anular un método de clase padre. En el libro "Programación en Scala, 2ª edición", el autor escribe que (con modificaciones aquí) Si no hubiera un método f (), la implementación original del método f () por parte del cliente no podría haber tenido un modificador override. Una vez que agregue el método f () a la segunda versión de su clase de biblioteca, una recompilación del código del cliente daría un error de compilación en lugar de un comportamiento incorrecto.
- Julia solo permite la subtipificación de tipos abstractos y utiliza la composición como alternativa a la herencia . Sin embargo, tiene envío múltiple .
Ver también
Referencias
enlaces externos
- Mikhajlov, Leonid; Sekerinski, Emil (1998). "Un estudio del problema de la clase base frágil" (PDF) . ECOOP'98 - Programación orientada a objetos . ECOOP 1998. LCNS . 1445 . págs. 355–382. doi : 10.1007 / BFb0054099 . ISSN 0302-9743 . QID 29543920 . Consultado el 21 de julio de 2020 .
- Holub, Allen (1 de agosto de 2003). "Por qué se extiende es el mal" . Caja de herramientas de Java. JavaWorld . Consultado el 21 de julio de 2020 .