Introducción al principio
Acuñado por Barbara Liskov en el año 1987 durante una conferencia sobre Jerarquía y Abstracción de datos.
Este principio indica lo siguiente:
Los subtipos deberían poder ser reemplazables por sus tipos base
Lo que queremos es algo parecido a la siguiente propiedad sustitutiva: Si por cada objeto O1 de tipo S hay un objeto O2 de tipo T tal que todos los programas P estan definidos en términos de T, el comportamiento de P no cambia cuando O1 es sustituido por O2 siendo S un subtipo de T
En definitiva este principio indica que dos clases compartiendo abstracción deben poder ser intercambiables en los clientes que las usan. Si al sustituir una por otra se produce una excepción o el comportamiento en el cliente cambia, no se esta cumpliendo el principio.
Ejemplo visual
Parece un pato, suena como un pato, pero el de goma, necesita pilas y no come.
Ejemplo de código
Vamos a ver el clásico ejemplo de cuadrado y rectángulo.
Ejemplo de herencia
Primero tenemos un Rectangulo:
public class Rectangulo
{
private int ancho;
private int alto;
public virtual int Ancho
{
get { return ancho; }
set
{
ancho = value;
}
}
public virtual int Alto
{
get { return alto; }
set
{
alto = value;
}
}
}
Y una clase cuadrado que hereda de Rectángulo, pero en un cuadrado los lados son iguales:
public class Cuadrado : Rectangulo
{
public override int Ancho
{
get { return base.Ancho;}
set
{
base.Ancho = value;
base.Alto = value;
}
}
public override int Alto
{
get { return base.Alto; }
set
{
base.Ancho = value;
base.Alto = value;
}
}
}
Si escribimos unos test vemos que sucede:
[TestMethod]
public void AreaRectangulo()
{
Rectangulo r = new Rectangulo { Ancho = 5, Alto = 2 };
// el test pasa
Assert.AreEqual(r.Ancho * r.Alto, 10); // true
}
[TestMethod]
public void AreaRectangulo()
{
Rectangulo r = new Cuadrado{ Ancho = 5, Alto = 2 };
Assert.AreEqual(r.Ancho * r.Alto, 10); // false
}
El segundo tes fallará porque cuadrado establerá 2 el ancho y el alto y el resultado no será 10.
Ejemplo de interface
Ahora vamos a ver un ejemplo de interface
public interface IProductService
{
IResult Update(IEnumerable<Product> products);
List<ProductInfoCA> GetFilteredProductList
(int pageNumber, int pageSize);
}
public class APIProductClient: IProductService
{
public IResult Update(IEnumerable<Product> products);
{
//code
}
public List<Product> GetFilteredProductList
(int pageNumber, int pageSize);
{
//code
}
}
public class FTPProductClient: IProductService
{
public IResult Update(IEnumerable<Product> products);
{
//code
}
public List<Product> GetFilteredProductList
(int pageNumber, int pageSize);
{
throw new NotImplementedException();
}
}
Cuando creamos una implementación y no queda más remedio que lanzar una excepción porque la implementación no tiene esa funcionalidad, estamos incumpliendo el principio y las dos implementaciones no se pueden sustituir sin tener consecuencias en el cliente.
Si el cliente que trabajaba con APIProductClient le sustituimos la dependencia por FTPProductClient, al llamar al método GetFilteredProductList se va a producir una excepción.
Curso y artículos relacionados
Conclusiones
Aplicando el Principio de Sustitución de Liskov conseguimos un código menos frágil a cambios, porque aseguramos que las abstracciones son correctas, por lo tanto es difícil estropear código que funciona.
Futuras implementaciones de las abstracciones podrán ser intercambiadas por las actuales sin que eso suponga un problema en el cliente de la abstracción.