L - Liskov Substitution Principle

· 2 min de lectura
L - Liskov Substitution Principle

La definición académica puede asustar: "Las clases derivadas deben poder sustituirse por sus clases base sin alterar el correcto funcionamiento del programa".

En español sencillo, esto significa que si tu código espera un Libro, debería funcionar con CUALQUIER tipo de libro que te inventes (físico, digital o audiolibro) sin que el sistema explote. Si tienes que usar un if para preguntar "¿qué tipo de libro eres?" antes de usarlo, algo anda mal.

El Caso de Estudio: "La Era Digital"

El CEO de nuestra startup llega eufórico. Ha decidido que el papel es cosa del pasado y quiere que vendamos E-books para mañana mismo.

Tú piensas: "Fácil, un E-book ES UN libro". Así que usas la herencia. Pero hay un detalle: tu clase original Libro tiene un método para calcular el costo de envío, porque hasta ahora todos los libros eran de papel y se enviaban por correo.

// Clase Base Original
public class Libro {
    // título, autor, etc. 
    public double calcularCostoEnvio() {
        return 5.00; // Costo estándar por correo 
    }
}

Ahora creas la subclase LibroDigital. Pero claro, los PDFs no se envían por correo, ¡se descargan!. Para salir del paso, haces esto:

// ❌ VIOLACIÓN LSP: ¡Rompemos el comportamiento esperado!
public class LibroDigital extends Libro {
    @Override
    public double calcularCostoEnvio() {
        throw new UnsupportedOperationException("¡Los E-books no se envían!"); [cite: 290]
    }
}

El desastre en el carrito de compras

Todo parece estar bien hasta que un cliente intenta comprar "El Quijote" (físico) y "Aprende Java" (digital) al mismo tiempo. Tu sistema de cobro recorre la lista de libros para sumar el total:

public double calcularTotalCarrito(List<Libro> listaDeLibros) {
    double total = 0;
    for (Libro libro : listaDeLibros) {
        // 💥 ¡PUM! Aquí explota todo cuando llega el LibroDigital
        total += libro.getPrecio() + libro.calcularCostoEnvio();
    }
    return total;
}

El sistema intenta calcular el envío del LibroDigital, choca con tu excepción y toda la compra se cancela. Has violado el principio de Liskov porque tu "clase hija" es una rebelde: cuando le pides algo que todos los libros deberían saber hacer, te lanza un error a la cara.

La Solución : No fuerces la herencia

El error fue asumir que un LibroDigital es exactamente igual a un Libro que se envía. Para arreglarlo, debemos rediseñar la familia de clases de forma veraz.

1. Creamos una base común y abstracta: Separamos lo que todos tienen (Título, Precio) de lo que es específico (Envío).

public abstract class ProductoLiterario {
    public abstract String getTitulo();
    public abstract double getPrecio();
}

2. Definimos quién es "Enviable": Creamos una interfaz solo para aquello que se puede enviar físicamente.

// CLASE B
public interface Enviable {
    double calcularCostoEnvio();
}

3. Implementamos las clases con honestidad: Ahora cada uno hace solo lo que realmente puede hacer.

public class LibroFisico extends ProductoLiterario implements Enviable {
    @Override
    public double calcularCostoEnvio() { return 5.00; } 
}

public class LibroDigital extends ProductoLiterario {
    // Solo lógica de descarga, nada de envíos
}

La moraleja de Liskov es sencilla: Si tienes que anular un método de la clase padre para lanzar un error o dejarlo vacío porque "no aplica", tu diseño está mal.

Un pato de goma se parece a un pato real, pero si intentas obligarlo a volar como uno de verdad, te vas a decepcionar. No obligues a tus clases a ser lo que no son.