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