Logotipo SOLID
Getting your Trinity Audio player ready...

En el capítulo L, hablaremos del Principio de Sustitución de Liskov, una idea formulada por Barbara Liskov y Jeannette M. Wing, en su artículo A Behavioral Notion of Subtyping de 1994. Si quieres entender cómo formalizar los conceptos abstractos que manejamos, te recomiendo que lo leas.

Este principio está escrito con varias fórmulas que no entraremos a detallar, pero que de forma resumida dirían lo siguiente:

Dado un conjunto de clases que heredan de otra. Todas las clases hijas pueden ser sustituidas por la clase madre, y viceversa, sin requerir de cambios en el código que las usa.

En otras palabras, cualquier clase o sus subclases en una relación de herencia deberían ser intercambiables sin afectar la funcionalidad de la aplicación.

La violación del principio de sustitución de Liskov se puede detectar cuando las clases hijas contienen métodos vacíos, devuelven valores sin sentido o lanzan excepciones que indican que no están implementados.

Para entender mejor este principio, consideremos el ejemplo de una clase Vehículo que tiene las operaciones acelerar() y volar(). Si queremos usar estas operaciones en una función despegar(), deberíamos ser capaces de hacerlo con cualquier objeto de tipo Vehículo o sus subtipos, sin importar la clase específica del objeto.

class Vehiculo {
  public acelerar(): void { console.log("Estoy acelerando"); }
  public volar(): void { console.log("Estoy volando"); }
}

class Coche extends Vehiculo {
  public acelerar(): void { console.log("Soy un coche que acelera"); }
  public volar(): void { throw new Error("Los coches no pueden volar"); } // ...de momento
}

function despegar(vehiculo: Vehiculo) {
  vehiculo.acelerar();
  vehiculo.volar();
}

const vehiculo: Vehiculo = new Vehiculo();
despegar(vehiculo);
// Estoy acelerando
// Estoy volando

const coche: Coche = new Coche();
despegar(coche);
// Soy un coche que acelera
// Uncaught Error: Los coches no pueden volar

En el código anterior, podemos observar que si se recibe un objeto de tipo Coche en la función despegar(), se producirá una excepción y se romperá el flujo de ejecución. Esto significa que tendríamos que cambiar el código de despegar() para adaptarlo al comportamiento particular de la clase hija.

Sin embargo, este enfoque viola el principio de sustitución de Liskov, que es fundamental para mantener la flexibilidad y la extensibilidad del código. Es más, no solo estamos incumpliendo dicho principio, sino que también estamos violando el principio Abierto/Cerrado, que establece que un módulo debe estar abierto para su extensión pero cerrado para su modificación.

Para resolver este problema, debemos buscar un comportamiento común para todas las clases que se utilizan en la función despegar(). En el ejemplo dado, podemos crear una nueva clase hija llamada VehículoVolador que extienda de la clase Vehículo y que implemente el método volar(). De esta manera, podemos mantener la funcionalidad común en la clase Vehículo y agregar el comportamiento específico en las clases hijas.

class Vehiculo {
  public acelerar(): void { console.log("Estoy acelerando"); }
}

class VehiculoVolador extends Vehiculo {
  public volar(): void { console.log("Estoy volando"); }
}

class Avion extends VehiculoVolador {
  public acelerar(): void { console.log("Soy un avión que acelera"); }
  public volar(): void { console.log("Libre al fin..."); }
}

function despegar(volador: VehiculoVolador ) {
  volador.acelerar();
  volador.volar();
}

const volador: VehiculoVolador = new VehiculoVolador();
despegar(volador);
// Estoy acelerando
// Estoy volando

const avion: Avion = new Avion();
despegar(avion);
// Soy un avión que acelera
// Libre al fin...

Ahora al añadir clases como: Dron, Aeroplano, Helicóptero, etc. Si hacemos que todas ellas hereden de VehículoVolador, ya no será necesario modificar el código de la función despegar().

Con esto terminamos el tercero de los principios SOLID, en el capítulo siguiente pasaremos a ver el Principio de Segregación de Interfaces. Y si queréis un resumen de todo esto, podéis verlo en Instagram.


Más artículos de esta serie:
Capítulo S: Single Responsibility Principle
Capítulo O: Open/Closed Principle
Capítulo I: Interface-Segregation Principle
Capítulo D: Dependency Inversion Principle

Si quieres leer algo más de Barbara Liskov, tiene un libro muy completo para aquellas personas que programan en Java: Program Development in Java: Abstraction, Specification, and Object-Oriented Design.

Comparte este artículo con quien quieras
Principios SOLID. Capítulo O: Open/Closed Principle
Principios SOLID. Capítulo I: Interface-Segregation Principle

Leave a Comment

Your email address will not be published. Required fields are marked *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.