SOLIDPrincipios

Sustitución de Liskov (LSP)

Los objetos de una subclase deben poder reemplazar a los de su clase base sin alterar el comportamiento correcto del programa.

¿Qué es?

El Liskov Substitution Principle dice que si B es una subclase de A, cualquier código que funcione con A debe seguir funcionando igual de bien si en su lugar se le pasa un B. Si una subclase cambia el comportamiento esperado (lanza errores donde no se esperaban, ignora parámetros, devuelve otra cosa), rompe el contrato y viola LSP.

Antes: la subclase rompe el contrato

class Rectangle {
  constructor(protected width: number, protected height: number) {}

  setWidth(width: number): void {
    this.width = width;
  }

  setHeight(height: number): void {
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }
}

// Un cuadrado "es un" rectángulo... pero no se comporta igual
class Square extends Rectangle {
  setWidth(width: number): void {
    this.width = width;
    this.height = width; // efecto colateral inesperado
  }

  setHeight(height: number): void {
    this.width = height;
    this.height = height; // efecto colateral inesperado
  }
}

function testArea(rect: Rectangle) {
  rect.setWidth(5);
  rect.setHeight(4);
  console.log(rect.getArea()); // se espera 20
}

testArea(new Rectangle(0, 0)); // 20 ✅
testArea(new Square(0, 0)); // 16 ❌ — rompe la expectativa

Square hereda de Rectangle pero no puede sustituirlo: cualquier código escrito para Rectangle deja de dar el resultado esperado cuando recibe un Square.

Después: modelar el contrato correcto

interface Shape {
  getArea(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}

  getArea(): number {
    return this.width * this.height;
  }
}

class Square implements Shape {
  constructor(private side: number) {}

  getArea(): number {
    return this.side ** 2;
  }
}

function printArea(shape: Shape) {
  console.log(shape.getArea());
}

printArea(new Rectangle(5, 4)); // 20 ✅
printArea(new Square(4)); // 16 ✅ — comportamiento consistente con su propio contrato

En vez de forzar una relación de herencia que no reflejaba la realidad, Rectangle y Square pasan a implementar una interfaz común (Shape) con un contrato mínimo que ambas cumplen sin sorpresas.

A tener en cuenta: Una señal de alerta de que se está violando LSP: una subclase que sobreescribe un método solo para lanzar un error ("no soportado") o para que haga menos de lo que prometía la clase base.
herenciacontratosLSP