Básicamente queremos que las cosas se comporten con sensatez.
Considere el siguiente problema:
Me dan un grupo de rectángulos y quiero aumentar su área en un 10%. Entonces, lo que hago es establecer la longitud del rectángulo a 1.1 veces lo que era antes.
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
rectangle.Length = rectangle.Length * 1.1;
}
}
Ahora, en este caso, todos mis rectángulos ahora tienen su longitud aumentada en un 10%, lo que aumentará su área en un 10%. Desafortunadamente, alguien realmente me pasó una mezcla de cuadrados y rectángulos, y cuando cambió la longitud del rectángulo, también lo hizo el ancho.
Mis pruebas unitarias pasan porque escribí todas mis pruebas unitarias para usar una colección de rectángulos. Ahora he introducido un error sutil en mi aplicación que puede pasar desapercibido durante meses.
Peor aún, Jim de contabilidad ve mi método y escribe otro código que usa el hecho de que si pasa cuadrados a mi método, obtiene un aumento de tamaño del 21%. Jim es feliz y nadie es más sabio.
Jim es promovido por su excelente trabajo a una división diferente. Alfred se une a la empresa como junior. En su primer informe de error, Jill de Advertising ha informado que pasar cuadrados a este método resulta en un aumento del 21% y quiere que se solucione el error. Alfred ve que los cuadrados y los rectángulos se usan en todas partes del código y se da cuenta de que es imposible romper la cadena de herencia. Tampoco tiene acceso al código fuente de Contabilidad. Entonces Alfred corrige el error así:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle)
{
rectangle.Length = rectangle.Length * 1.1;
}
if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Alfred está contento con sus habilidades de piratería súper y Jill firma que el error está solucionado.
El mes próximo, a nadie se le paga porque la Contabilidad dependía de poder pasar cuadrados al IncreaseRectangleSizeByTenPercent
método y obtener un aumento en el área del 21%. Toda la empresa pasa al modo "corrección de errores de prioridad 1" para rastrear el origen del problema. Ellos rastrean el problema hasta la solución de Alfred. Saben que deben mantener contentos tanto a la contabilidad como a la publicidad. Entonces arreglan el problema identificando al usuario con la llamada al método de la siguiente manera:
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles)
{
IncreaseRectangleSizeByTenPercent(
rectangles,
new User() { Department = Department.Accounting });
}
public void IncreaseRectangleSizeByTenPercent(IEnumerable<Rectangle> rectangles, User user)
{
foreach(var rectangle in rectangles)
{
if (typeof(rectangle) == Rectangle || user.Department == Department.Accounting)
{
rectangle.Length = rectangle.Length * 1.1;
}
else if (typeof(rectangle) == Square)
{
rectangle.Length = rectangle.Length * 1.04880884817;
}
}
}
Y así sucesivamente y así sucesivamente.
Esta anécdota se basa en situaciones del mundo real que enfrentan los programadores a diario. Las violaciones del principio de sustitución de Liskov pueden introducir errores muy sutiles que solo se detectan años después de que se escriben, momento en el que corregir la violación romperá un montón de cosas y no arreglarlo enojará a su mayor cliente.
Hay dos formas realistas de solucionar este problema.
La primera forma es hacer que Rectángulo sea inmutable. Si el usuario de Rectángulo no puede cambiar las propiedades Longitud y Ancho, este problema desaparece. Si desea un rectángulo con una longitud y ancho diferentes, cree uno nuevo. Los cuadrados pueden heredar de rectángulos felizmente.
La segunda forma es romper la cadena de herencia entre cuadrados y rectángulos. Si un cuadrado se define como tener una sola SideLength
propiedad y rectángulos tienen una Length
y Width
la propiedad y no hay herencia, que es imposible de romper accidentalmente cosas al esperar un rectángulo y conseguir una plaza. En términos de C #, podría seal
su clase de rectángulo, lo que garantiza que todos los Rectángulos que obtenga sean en realidad Rectángulos.
En este caso, me gusta la forma de "objetos inmutables" de solucionar el problema. La identidad de un rectángulo es su longitud y ancho. Tiene sentido que cuando quiera cambiar la identidad de un objeto, lo que realmente quiere es un objeto nuevo . Si pierde un antiguo cliente y gana un nuevo cliente, no cambia el Customer.Id
campo del antiguo cliente al nuevo, crea uno nuevo Customer
.
Las violaciones del principio de sustitución de Liskov son comunes en el mundo real, principalmente porque gran parte del código está escrito por personas incompetentes / bajo presión de tiempo / no les importa / cometen errores. Puede y causa algunos problemas muy desagradables. En la mayoría de los casos, desea favorecer la composición sobre la herencia .