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 en 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 han aumentado su longitud en un 10%, lo que aumentará su área en un 10%. Desafortunadamente, alguien realmente me ha pasado una mezcla de cuadrados y rectángulos, y cuando se cambió la longitud del rectángulo, también lo fue el ancho.
Mis pruebas de unidad pasan porque escribí todas mis pruebas de unidad 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 muy buen aumento de tamaño del 21%. Jim es feliz y nadie es más sabio.
Jim es promovido por un trabajo excelente a una división diferente. Alfred se une a la empresa como junior. En su primer informe de errores, Jill, de Advertising, informó que pasar cuadrados a este método da como resultado un aumento del 21% y quiere que se solucione el error. Alfred ve que los cuadrados y los rectángulos se utilizan en todas partes del código y se da cuenta de que romper la cadena de herencia es imposible. Tampoco tiene acceso al código fuente de Accounting. Así que Alfred arregla el error de esta manera:
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 hacker de uber y Jill firma que el error está solucionado.
El mes próximo no se paga a nadie porque la Contabilidad dependía de poder pasar cuadrados al método IncreaseRectangleSizeByTenPercent
y obtener un aumento en el área del 21%. Toda la compañía entra en el modo de "corrección de errores de prioridad 1" para rastrear el origen del problema. Ellos rastrean el problema a la solución de Alfred. Saben que tienen que mantener contentos tanto a la Contabilidad como a la Publicidad. Así que solucionan el problema identificando al usuario con el método llamado así:
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.
Esta anécdota se basa en situaciones reales 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 infracción romperá un montón de cosas y no arreglar enfadará a tu mayor cliente.
Hay dos formas realistas de solucionar este problema.
La primera forma es hacer que Rectangle sea inmutable. Si el usuario de Rectangle no puede cambiar las propiedades Longitud y Ancho, este problema desaparece. Si desea un rectángulo con una longitud y anchura diferentes, cree uno nuevo. Los cuadrados pueden heredar de los rectángulos alegremente.
La segunda forma es romper la cadena de herencia entre cuadrados y rectángulos. Si un cuadrado se define como tener una sola propiedad SideLength
y los rectángulos tienen una propiedad Length
y Width
y no hay herencia, es imposible romper cosas accidentalmente esperando un rectángulo y obteniendo un cuadrado. En términos de C #, podría seal
su clase de rectángulo, lo que garantiza que todos los Rectángulos que obtenga sean realmente 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 anchura. Tiene sentido que cuando desee cambiar la identidad de un objeto, lo que realmente desea sea un objeto nuevo . Si pierde un cliente antiguo y obtiene un cliente nuevo, no cambia el campo Customer.Id
del cliente anterior al nuevo, crea un nuevo Customer
.
Las violaciones del principio de Sustitución de Liskov son comunes en el mundo real, principalmente porque la mayoría de los códigos están escritos por personas que son incompetentes / bajo presión de tiempo / no les importa / cometen errores. Puede y lleva a algunos problemas muy desagradables. En la mayoría de los casos, desea favorecer la composición sobre la herencia en su lugar.