Definición y concepto

El principio de sustitución de Liskov, conocido internacionalmente por sus siglas en inglés como LSP, constituye uno de los pilares fundamentales de la programación orientada a objetos. Este concepto establece directrices esenciales para garantizar la coherencia y la flexibilidad en la jerarquía de clases, permitiendo que los sistemas de software sean más robustos y fáciles de mantener. La comprensión precisa de este principio es vital para los desarrolladores que buscan evitar errores sutiles en la herencia y la polimorfía.

Definición práctica

En términos operativos, el principio establece que cada clase que hereda de otra puede usarse como su padre sin necesidad de conocer las diferencias entre ellas. Esto significa que el código cliente que depende de una clase base debe poder funcionar correctamente al recibir una instancia de cualquier clase derivada, sin requerir modificaciones adicionales ni lógica condicional específica para distinguir entre el tipo base y el tipo derivado. La transparencia en la sustitución es clave: el comportamiento esperado del objeto base debe conservarse al ser reemplazado por su subtipo.

Esta definición práctica enfatiza la usabilidad y la predictibilidad. Si una clase derivada introduce comportamientos que rompen las expectativas establecidas por la clase base, se incurre en una violación del principio. Por ejemplo, si una clase base define un método que devuelve un valor específico bajo ciertas condiciones, una clase derivada no debería alterar ese resultado de manera que el código que utiliza la clase base falle o se comporte de forma inesperada.

Formulación formal

La definición formal del principio fue establecida rigurosamente por Barbara Liskov y Jeannette Wing. Según su formulación, si S es un subtipo de T, los objetos de tipo T pueden ser sustituidos por objetos de tipo S sin alterar propiedades deseables del programa, tales como la corrección. Esta definición introduce un nivel de abstracción matemática que permite analizar la relación de herencia más allá de la simple sintaxis de las clases.

La noción de "propiedades deseables" hace referencia a los invariantes y las condiciones que deben mantenerse verdaderas a lo largo de la ejecución del programa. La corrección del programa depende de que estas propiedades no se vean afectadas por la sustitución. Esto implica que la relación de subtipado no es solo una relación estructural (como la inclusión de miembros), sino también una relación comportamental. La formalización de Liskov y Wing proporciona una base sólida para verificar si una relación de herencia es válida desde una perspectiva lógica y semántica.

Relación con la lógica de Hoare y el diseño por contrato

El principio de sustitución de Liskov se basa profundamente en la lógica de Hoare y en el concepto de diseño por contrato. La lógica de Hoare ofrece un marco formal para describir el comportamiento de los programas mediante precondiciones y postcondiciones. En el contexto del LSP, esto se traduce en reglas específicas para las clases derivadas respecto a las clases base.

Según estas reglas, las precondiciones de una clase derivada no deben ser más estrictas que las de la clase base. Esto significa que la clase derivada debe aceptar al menos los mismos argumentos o estados que la clase base. Por otro lado, las postcondiciones de la clase derivada no deben ser más débiles que las de la clase base, lo que implica que la clase derivada debe garantizar al menos los mismos resultados o estados finales. Estas restricciones aseguran que la sustitución no rompa las expectativas establecidas por el contrato de la clase base, manteniendo así la integridad del sistema.

Historia y contexto académico

El principio de sustitución de Liskov (LSP) se enmarca dentro de los fundamentos teóricos de la programación orientada a objetos, estableciendo criterios rigurosos para la relación entre tipos y sus subtipos. Su desarrollo histórico refleja una evolución conceptual que pasó de una intuición práctica a una formulación formalizada, integrando conceptos de la lógica matemática y la teoría de tipos para garantizar la robustez del software.

Origen del concepto: La conferencia de 1987

El concepto fue introducido inicialmente por la informática Barbara Liskov en 1987. Este anuncio se realizó durante una conferencia magistral titulada «La Abstracción de Datos y Jerarquía». En este contexto académico, Liskov presentó la idea central de que las subclases deben ser sustituibles por sus clases base sin alterar la corrección del programa. Esta presentación marcó un punto de inflexión en la comprensión de las jerarquías de clases, desplazando el foco desde la herencia de implementación hacia la herencia de interfaz y comportamiento.

Formalización académica y Lógica de Hoare

Aunque la idea fue propuesta en 1987, la definición formal y ampliamente aceptada del principio fue establecida posteriormente. En 1994, Barbara Liskov y Jeannette Wing formularon conjuntamente el principio, proporcionando una base teórica sólida que ha perdurado en la literatura científica. Esta formulación se basa explícitamente en la lógica de Hoare y en el concepto de diseño por contrato, herramientas fundamentales para especificar el comportamiento de los módulos de software.

Esta definición subraya que el LSP se refiere a una relación semántica más que sintáctica. Mientras que la sintaxis garantiza que los métodos existen y tienen los mismos nombres, la semántica garantiza que el comportamiento sea coherente con las expectativas establecidas por la clase base.

Al garantizar la interoperabilidad semántica de tipos en una jerarquía, el principio asegura que las precondiciones de los métodos en la subclase no sean más estrictas que las de la clase base, y que las postcondiciones no sean más débiles. Esto permite que los objetos de la subclase sean utilizados en cualquier contexto donde se espere un objeto de la clase base, manteniendo la invariante del sistema y reduciendo la complejidad cognitiva para los desarrolladores que trabajan con la jerarquía.

Formulación formal del principio

La formulación precisa del principio de sustitución de Liskov fue establecida conjuntamente por Barbara Liskov y Jeannette Wing en 1994. Esta definición formal proporcionó un marco riguroso para comprender cómo los subtipos deben comportarse en relación con sus tipos base, superando las definiciones más intuitivas anteriores.

Definición formal de Liskov y Wing

La definición establece lo siguiente: "Sea ϕ(x) una propiedad comprobable acerca de los objetos x de tipo T. Entonces ϕ(y) debe ser verdad para los objetos y del tipo S, donde S es un subtipo de T". Esta formulación implica que cualquier propiedad demostrable sobre los objetos de un tipo base debe mantenerse válida para los objetos de cualquier subtipo derivado.

En términos prácticos, esto significa que si un programa utiliza objetos de tipo T y depende de ciertas propiedades ϕ(x), al sustituir esos objetos por instancias de un subtipo S, las mismas propiedades deben seguir siendo ciertas para los objetos y de tipo S. La propiedad ϕ(x) representa cualquier comportamiento observable o invariante que el código cliente espera del tipo T.

Relación con la tipificación del comportamiento

El principio de sustitución de Liskov constituye una definición particular de una relación de subtipificación conocida como tipificación (fuerte) del comportamiento. Esta noción de subtipificación se basa en la lógica de Hoare y el diseño por contrato, proporcionando una base matemática sólida para la herencia en la programación orientada a objetos.

Bajo esta perspectiva, un tipo S es considerado un subtipo de T si y solo si los objetos de S pueden reemplazar a los objetos de T sin alterar las propiedades deseables del programa. Esta relación de subtipificación del comportamiento va más allá de la simple estructura de datos, incorporando aspectos dinámicos del comportamiento de los objetos.

La conexión con la lógica de Hoare permite expresar formalmente las precondiciones, postcondiciones e invariantes que deben preservarse durante la sustitución. El diseño por contrato, por su parte, ofrece un mecanismo práctico para especificar estas propiedades mediante acuerdos explícitos entre el objeto y su cliente, facilitando la verificación del cumplimiento del principio.

¿Cómo afectan las precondiciones y postcondiciones al LSP?

El Principio de Sustitución de Liskov (LSP) encuentra su fundamento teórico en la lógica de Hoare y se operationaliza a través del diseño por contrato, un concepto introducido por Bertrand Meyer. Esta relación es esencial para garantizar que los subtipos mantengan el comportamiento esperado de los supertipos sin romper la transparencia del cliente. El diseño por contrato establece acuerdos explícitos entre las clases, definiendo qué se exige antes de la ejecución (precondiciones) y qué se garantiza después (postcondiciones).

Reglas de precondiciones y postcondiciones

Para que un subtipo sea una sustitución válida de su supertipo, debe respetar reglas estrictas sobre cómo modifica estas condiciones. Según la formulación de Liskov y Wing, las precondiciones en el subtipo no pueden ser más restrictivas que las del supertipo. Esto significa que si un método del padre acepta un rango amplio de valores, el hijo no puede exigir un subconjunto más pequeño sin riesgo de que el cliente pase un valor válido para el padre pero inválido para el hijo.

Por el contrario, las postcondiciones en el subtipo deben ser al menos tan fuertes como las del supertipo. Si el método del padre garantiza un resultado dentro de cierto rango, el hijo debe garantizar ese mismo rango o uno más específico. Si el hijo devuelve un resultado más amplio o menos definido, se dice que la postcondición se ha debilitado, rompiendo la expectativa del cliente.

Concepto Regla LSP Implicación en el subtipo
Precondiciones No reforzadas El subtipo debe aceptar al menos lo que acepta el supertipo.
Postcondiciones No debilitadas El subtipo debe garantizar al menos lo que garantiza el supertipo.

Estas reglas aseguran que la sustitución sea transparente. Si un cliente ha programado contra la interfaz del supertipo, asume que las precondiciones son las mínimas necesarias y las postcondiciones son las máximas garantizadas. Cualquier desviación que haga las precondiciones más duras o las postcondiciones más suaves introduce errores en tiempo de ejecución que el cliente podría no haber anticipado, violando así el principio de que el comportamiento del programa no debe alterarse al sustituir el supertipo por su subtipo.

¿Qué papel juegan las invariantes y la restricción histórica?

Las invariantes constituyen un pilar fundamental en la aplicación del Principio de sustitución de Liskov. Una invariante es una condición lógica que debe permanecer verdadera durante toda la vida útil de un objeto, salvo cuando se ejecutan sus propios métodos. Según la definición formal establecida por Barbara Liskov y Jeannette Wing, si una clase padre establece ciertas invariantes sobre su estado interno, cualquier subclase que herede de ella debe garantizar que esas mismas condiciones se mantengan tras la ejecución de cualquier método heredado o sobrescrito. Esto asegura que el comportamiento observable del objeto no se desvíe de lo esperado por el cliente que interactúa con la jerarquía.

La restricción histórica y el encapsulamiento

Para reforzar la coherencia de las jerarquías de clases, Liskov y Wing introdujeron la llamada restricción histórica, también conocida como regla histórica. Esta norma establece que los objetos deben modificarse exclusivamente a través de sus métodos públicos, lo cual refuerza el concepto de encapsulamiento en la programación orientada a objetos. Bajo esta restricción, el estado interno de un objeto no debería ser alterado directamente por factores externos o por subtipos que accedan a atributos protegidos sin pasar por la lógica definida en la interfaz del supertipo.

El cumplimiento de la restricción histórica previene situaciones en las que un subtipo pueda introducir nuevos métodos que modifiquen el estado interno de formas que serían inadmisibles o inesperadas para el supertipo. Si un subtipo permite cambios de estado que violan las invariantes originales del padre, se rompe la sustitución transparente. Por lo tanto, cualquier modificación introducida por los subtipos debe ser compatible con las precondiciones, postcondiciones e invariantes definidas inicialmente, garantizando así que la lógica de Hoare y el diseño por contrato se mantengan intactos a lo largo de toda la jerarquía de herencia.

Ejercicios resueltos

Ejercicio 1: Análisis de la violación clásica del principio

Se analiza el caso histórico de un subtipo PuntoMutable que hereda de PuntoInmutable. En el supertipo, el método setX(x) establece la coordenada X sin afectar a Y. Sin embargo, en el subtipo, para mantener una invariante (por ejemplo, que el punto esté en la línea y = x), el método setX(x) también modifica internamente la coordenada Y. Esto implica que el estado del objeto cambia en dimensiones no esperadas por el cliente del supertipo.

Esta situación viola el Principio de Sustitución de Liskov (LSP) porque el comportamiento del subtipo no es consistente con las expectativas definidas por el contrato del padre. Según la formulación de Liskov y Wing, las precondiciones no deben ser más fuertes ni las postcondiciones más débiles. Aquí, la postcondición de setX en PuntoInmutable es que solo X cambia; en PuntoMutable, cambian X e Y, debilitando la garantía del contrato original.

Ejercicio 2: Caso válido con extensión de estado

Se considera un tipo Círculo derivado de PuntoInmutable, donde el círculo tiene un centro fijo (heredado) y un radio variable. Se debe determinar si esto viola LSP. La clave está en la visibilidad de los campos adicionales. Si el radio es un campo extra que no es accesible directamente a través de los métodos definidos en PuntoInmutable (como getX() o getY()), entonces el comportamiento del supertipo se mantiene intacto.

En este escenario, las operaciones sobre el centro del círculo siguen cumpliendo las invariantes del punto. El hecho de que Círculo tenga un estado adicional (el radio) no afecta las garantías de PuntoInmutable, siempre que los métodos heredados no dependan implícitamente de la ausencia de ese radio. Por lo tanto, no hay violación de LSP porque los campos extra no son observables desde la interfaz del supertipo, respetando así la lógica de Hoare subyacente al diseño por contrato.

Aplicaciones en programación orientada a objetos

El principio de sustitución de Liskov (LSP) es fundamental para asegurar que los objetos de un tipo derivado S puedan sustituir a los objetos de un tipo base T sin alterar la corrección del programa. Esta garantía no depende únicamente de la estructura de datos o de la firma de los métodos, sino de la semántica del comportamiento esperado. En la programación orientada a objetos, la sustitución exitosa implica que cualquier operación válida sobre una instancia de T debe producir resultados coherentes cuando se ejecuta sobre una instancia de S.

Interoperabilidad semántica frente a relaciones sintácticas

Una distinción crítica en la aplicación del LSP es la diferencia entre la compatibilidad sintáctica y la interoperabilidad semántica. La relación de herencia clásica (por ejemplo, extends o is-a) establece principalmente una relación sintáctica: la clase hija hereda atributos y métodos de la clase padre. Sin embargo, el LSP exige que esta relación sea también semántica. Es decir, el comportamiento observable de la subclase debe ser consistente con las expectativas establecidas por la superclase.

Si una subclase modifica el comportamiento de un método de forma que rompe las suposiciones hechas por el código cliente que utiliza la superclase, se viola el principio, incluso si la firma del método (nombre y parámetros) permanece inmutable. Esto resalta que la herencia no es solo un mecanismo de reutilización de código, sino un contrato de comportamiento.

Implicaciones en precondiciones, postcondiciones e invariantes

Para formalizar esta interoperabilidad, la definición de Liskov y Wing utiliza conceptos de la lógica de Hoare y el diseño por contrato. El principio establece restricciones específicas sobre cómo las subclases deben refinar los contratos de las superclases:

Al respetar estas reglas, se garantiza que los objetos de tipo S puedan usarse como su padre sin necesidad de conocer las diferencias entre ellas, manteniendo la coherencia lógica y la robustez del sistema.

Véase también

Referencias

  1. «Principio de sustitución de Liskov» en Wikipedia en español
  2. Barbara Liskov's Original Paper: Data Abstraction and Hierarchy
  3. Liskov Substitution Principle - Microsoft Learn (Official Documentation)
  4. The Liskov Substitution Principle - IBM Developer