c# Object oriented design – ¿Cómo aplicas el principio Liskov Substitution?

Si alguna vez te has preguntado ¿Qué es el principio Liskov Substitution? y ¿Cómo lo puedo implementar? te recomiendo que leas este post.
Principio Liskov Substitution
Principio Liskov Substitution


Artículos relacionados:

El concepto de este principio fue introducido por Barbara Liskov en una conferencia de 1987 llamada Data abstraction and hierarchy y en el año 1994 junto a Jeannette Wing formularon este principio. Pero fue Robert C. Martin  quien lo popularizó en su libro Agile Principles, Patterns, and Practices in C# llamandolo Liskov Substitution. Este es el tercer de los cinco principios SOLID.

Empecemos con la definición original que dio Barbara Liskov y Jeannette Wing:

Sea q (x) una propiedad comprobable acerca de los objetos x de tipo T. Entonces q (y) debe ser verdad para los objetos y del tipo S donde S, es un subtipo de T.

Wikipedia lo describe en un lenguaje más formal:

Si S es un subtipo de T, entonces los objetos de tipo T en un programa de pueden ser sustituidos por objetos de tipo S (es decir, los objetos de tipo S pueden sustituir objetos de tipo T), sin alterar ninguna de las propiedades deseables de ese programa.

Substitución de tipos
Nik Boyd – Software Metaphors – Substitución de tipos

Robert C. Martin le da la siguiente definición:

Subtypes must be substitutable for their base types. 

Este principio es una extensión del principio Open Closed y señala que una clase derivada puede ser reemplazada por cualquier otra que use su clase base sin alterar el correcto funcionamiento de un programa. Este principio es una definición particular de una relación entre subtipos, ya que si una función espera como parámetro una clase base esta puede ser reemplazada por cualquier clase derivada. Para no alterar el adecuado funcionamiento de un programa, una subclase no debe remover comportamiento de la clase base, no debe conocer a los demás subtipos y no debe violar las pre-condiciones, post-condicionesinvariantes de la clase base. Por invariantes nos referimos a una condición que siempre se evalúa como verdadera en todas las instancias de una clase. Las clases hijas no deben lanzar nuevas excepciones, deben trabajar con los tipos que trabaja la clase base. Además, si una clase base valida que los parámetros de entrada no sean nulos y las clases no lo hacen, se está que viola este principio.

¿Sobre qué artefactos aplica este principio?
  • Clases
¿Qué beneficios trae el trabajar con este principio?
  • Flexibilidad.
  • Mantenimiento, sistemas fáciles de cambiar.
¿Cuándo debemos aplicar este principio?
  • Cuando se quiere extender el funcionamiento usando clases derivadas sin tocar el código base.
  • Cuando existan clases que compartan el mismo comportamiento.
  • Cuando aplicamos el principio Open Closed.
¿Cuándo no debemos aplicar este principio?
  • Cuando se sabe que una clase cuenta con un comportamiento fijo.
Argumentos en contra de este principio
  • Requiere mayor experiencia
Veamos el siguiente ejemplo:

Tenemos la clase base ReglaNegocio que tiene dos métodos Validar y Ejecutar. El método Validar verifica que el parámetro de entrada tenga un valor correcto sino arroja una excepción antes de ejecutar la validación de la regla de negocio, recordemos que este es el comportamiento que define la clase base y sus hijos deben respetarla. Ahora el metodo Ejecutar se debe llamar luego de invocar al método Validar.

public abstract class ReglaNegocio
{
    protected bool EsContextoValido;

    public virtual bool Validar(string identificador)
    {
        if (string.IsNullOrWhiteSpace(identificador)) 
            throw new ArgumentNullException();

        //Codigo
        return EsContextoValido;
    }
    public abstract void Ejecutar(string identificador);
}

Ahora revisemos la clase Cliente, el método Ejecutar recibe una instancia de la clase base ReglaNegocio. Esta clase espera que cualquier subtipo de ReglaNegocio cuente con la lógica necesaria para validar y ejecutar la regla. Si algún hijo no cumple con estas reglas estaría violando este principio.

public class Cliente
{
    private string NumeroDocumento;

    public void Ejecutar(ReglaNegocio regla)
    {
        if (regla.Validar(NumeroDocumento))
        {
            regla.Ejecutar(NumeroDocumento);
        }
    }
}
Cómo no aplicamos este principio

Si se pasa la clase ReglaNegocioValidarClienteError como referencia al método Ejecutar de la clase Cliente obtendremos como resultado un error que cambia el correcto funcionamiento del aplicativo. La clase base define que primero se deba llamar al método Validar para luego llamar al método Ejecutar, pero esta subclase no respeta esa regla.

public class ReglaNegocioValidarClienteError : ReglaNegocio
{
    public override bool Validar(string identificador)
    {
        throw new NotImplementedException();
    }

    public override void Ejecutar(string identificador)
    {
        //Codigo
    }
}

También estaríamos violando el principio si cambiamos la excepción por el siguiente código:

public override bool Validar(string identificador)
{
    if(string.IsNullOrWhiteSpace(identificador)) return false;

    //Codigo
}

En este caso al retornar False le indicamos al cliente que la validación de la regla de negocio no paso lo cual es mentira. Según la definición de la clase base debe devolver un error. De esta forma la subclase cambia el comportamiento de la clase base y viola el principio.

Como aplicamos este principio

En este caso tenemos la clase ReglaNegocioValidarClienteOk que sigue este principio, respeta las pre y post condiciones que especifica la clase base. Al pasar una referencia de este clase al método Ejecutar, de la clase Cliente, el comportamiento será el mismo.

public class ReglaNegocioValidarClienteOk : ReglaNegocio
{
    public override bool Validar(string identificador)
    {
        //Codigo
        return base.Validar(identificador);
    }

    public override void Ejecutar(string identificador)
    {
        if (EsContextoValido)
        {
            //Codigo
        }
    }
}
Conclusión

Este principio está muy ligado al anterior principio Open Closed. Una clase derivada puede ser reemplazada por cualquier otra que use su clase base sin alterar el correcto funcionamiento de un programa. Este principio solo se aplica a clases. Como principales beneficios tenemos que nos permite crear código flexible y fácil de mantener. Se debe aplicar cuando se quiere extender el comportamiento de una clase sin tocar el código base. También tiene argumentos en contra que señalan que se requiere experiencia para diseñar las clases.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando el disco Unplugged in New York de la banda Nirvana de Estados Unidos, les comparto el enlace.

Happy coding and Stay Heavy lml

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s