El problema de la clase base frágil es un problema arquitectónico fundamental de los sistemas de programación orientados a objetos , en los que las clases base ( superclases ) se consideran "frágiles" porque las modificaciones aparentemente seguras de una clase base, cuando son heredadas por las clases derivadas , pueden provocar que estas funcionen mal. El programador no puede determinar si un cambio en la 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 la clase que las define y obligar a las subclases a usar métodos de acceso 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 aquellos métodos de las superclases que sean aplicables a ellas mismas.
Una solución alternativa es tener una interfaz en lugar de una superclase.
El problema de la clase base frágil se ha atribuido a la recursión abierta (despacho dinámico de métodos en this
), con la sugerencia de que la invocación de métodos en this
por defecto a la recursión cerrada (despacho estático, enlace temprano) en lugar de la recursión abierta (despacho dinámico, enlace tardío), solo usando la recursión abierta cuando se solicita específicamente; las llamadas externas (sin usar this
) se enviarían dinámicamente como de costumbre. [1] [2]
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 provocar que una subclase heredada funcione mal al ingresar en una recursión infinita que dará como resultado un desbordamiento de pila .
clase Super { int privado contador = 0 ; void inc1 () { contador ++ ; } void inc2 () { contador ++ ; } } clase Sub extiende Super { @Override void inc2 () { inc1 (); } }
Al llamar al método inc2() enlazado dinámicamente en una instancia de Sub, se incrementará correctamente el contador de campo en uno. Sin embargo, si se cambia el código de la superclase de la siguiente manera:
clase Super { int privado contador = 0 ; vacío inc1 () { inc2 (); } void inc2 () { contador ++ ; } }
Una llamada al método inc2() enlazado dinámicamente en una instancia de Sub provocará una recursión infinita entre sí y el método inc1() de la superclase y, eventualmente, provocará un desbordamiento de pila. Este problema se podría haber evitado declarando los métodos en la superclase como final , lo que haría imposible que una subclase los anule. 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.
final
". En el libro Effective Java , el autor Joshua Bloch escribe (en el punto 17) que los programadores deberían "Diseñar y documentar la herencia o, de lo contrario, prohibirla".sealed
" y " Not Inheritable
" para prohibir la herencia y requieren que una subclase use la palabra clave " override
" para anular métodos, [3] la misma solución adoptada posteriormente por Scala.override
" explícitamente para anular un método de la 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() 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 del cliente arrojaría un error de compilación en lugar de un comportamiento incorrecto.open
modificador. Del mismo modo, un método debe estar marcado como open
para permitir la anulación del método.