Skip to main content

Inicia sesión en CleanKata

Sigue tu progreso, gana XP y desbloquea todas las lecciones.

Al iniciar sesión aceptas nuestros Términos de uso y Política de privacidad.

Arquitectura Limpia70 XP7 min

LSP: El Principio de Sustitución de Liskov

Los subtipos deben ser sustituibles por sus tipos base — las violaciones del LSP fuerzan lógica costosa de casos especiales en el diseño.

Por qué importa

La regla de sustitución de Barbara Liskov establece que si S es un subtipo de T, los objetos de tipo T pueden reemplazarse con objetos de tipo S sin alterar la corrección del programa. Esta no es solo una regla a nivel de clase — Martin muestra cómo las violaciones del LSP se propagan hasta el nivel arquitectónico.

El ejemplo arquitectónico involucra un servicio de despacho de taxis: si dos compañías de taxis exponen interfaces REST ligeramente diferentes, el sistema de despacho debe agregar un caso especial para una de ellas. Ese caso especial es una violación del LSP en la frontera del servicio. De manera similar, la clásica trampa Rectángulo/Cuadrado muestra cómo una subclase que cambia silenciosamente el comportamiento del padre rompe cualquier código que mantenga una referencia al tipo padre. La regla: si debes escribir if isinstance(obj, Subclass) antes de usarlo, el principio de sustitución ha sido violado y tu diseño acumulará lógica de despacho con el tiempo.

✗El problema

Square extends Rectangle but silently keeps both sides equal — code that widens a Rectangle produces wrong area when passed a Square.

Bad

class Rectangle:
    def __init__(self, w: float, h: float):
        self.width = w
        self.height = h

    def set_width(self, w: float) -> None:  self.width = w
    def set_height(self, h: float) -> None: self.height = h
    def area(self) -> float: return self.width * self.height

class Square(Rectangle):          # LSP violation
    def set_width(self, w: float) -> None:
        self.width = self.height = w   # silently overrides both!

    def set_height(self, h: float) -> None:
        self.width = self.height = h

def make_wider(r: Rectangle) -> None:
    r.set_width(r.height * 2)
    # Postcondition: width == height * 2 — FALSE when r is a Square
class Rectangle {
  constructor(protected width: number, protected height: number) {}
  setWidth(w: number): void  { this.width = w; }
  setHeight(h: number): void { this.height = h; }
  area(): number { return this.width * this.height; }
}

class Square extends Rectangle {      // LSP violation
  setWidth(w: number): void  { this.width = this.height = w; }
  setHeight(h: number): void { this.width = this.height = h; }
}

function makeWider(r: Rectangle): void {
  r.setWidth(r["height"] * 2);
  // Postcondition violated when r is actually a Square
}

✓La solución

Rectangle and Square both implement a common Shape interface independently — no problematic inheritance relationship between them.

Good

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: ...

class Rectangle(Shape):
    def __init__(self, w: float, h: float):
        self._width = w
        self._height = h

    def set_width(self, w: float) -> None:  self._width = w
    def set_height(self, h: float) -> None: self._height = h
    def area(self) -> float: return self._width * self._height

class Square(Shape):    # independent — no broken inheritance
    def __init__(self, side: float):
        self._side = side

    def set_side(self, s: float) -> None: self._side = s
    def area(self) -> float: return self._side ** 2
interface Shape {
  area(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  setWidth(w: number): void  { this.width = w; }
  setHeight(h: number): void { this.height = h; }
  area(): number { return this.width * this.height; }
}

class Square implements Shape {   // independent, no broken inheritance
  constructor(private side: number) {}
  setSide(s: number): void { this.side = s; }
  area(): number { return this.side ** 2; }
}

💡Conclusión clave

Si necesitas verificar el tipo de un objeto antes de usarlo, el LSP está violado — y tu arquitectura acumulará costosa lógica de despacho de casos especiales cada vez que se agregue un nuevo subtipo.

🔧 Algunos ejercicios pueden tener errores. Si algo parece incorrecto, usa el botón Feedback (abajo a la derecha) para reportarlo — nos ayuda a corregirlo rápido.

Pista: Si necesitas comprobar el tipo de un objeto antes de usarlo, el LSP está siendo violado.

✗ Tu versión