Predicados y especificaciones

Inspirado en este post, decidí hacerme un ejemplito de predicados en .Net y así utilizar el patrón especificaciones.

Un predicado es un delegado que apunta a funciones que devuelven un valor booleano y aceptan un objeto genérico.

Vamos a ver una forma interesante de hacer filtrados en base a ciertos criterios de selección utilizando estos dichosos predicados de .Net.

Ciertamente podríamos implementar esto de la forma que siempre se hace, realizar un filtrado, iterando en una colección, y preguntando si tal elemento, se corresponde con el criterio elegido. Por ejemplo: recorrer la lista y vamos preguntando: este cliente…tiene correo gmail ?

Podría esbozarse un código como este:

 static public IList<Cliente> HasGmail(IList<Cliente> lista)
        {
            IList<Cliente> lista_resultado = new List<Cliente>();

            foreach(Cliente c in lista)
            {
                if(c.Email.Contains("@gmail.com"))
                {
                    lista_resultado.Add(c);
                }
            }
            return lista_resultado;
        }

Y se llamaría así:

IList<Cliente> usuariosDeGmail = ClienteFinder.HasGmail(lista);

Pero ahora bien, se podría objetizar el código, y hacerlo un poco más flexible, y hacer uso de predicados.

Podríamos reemplazarlos por esto:

IList<Cliente> usuariosDeGmail = 
                new ClienteFinder(lista).Find(EmailSpec.HasGmail);

Parecería estar más complicado, pero el concepto es sencillo y aplica el patrón de especificaciones, mediante predicatos genéricos.

Vamos al codigo de la entidad de negocio de la cual tenemos un listado y la queremos obtener por un criterio: la entidad Cliente.

public class Cliente
    {
        public Cliente()
        { }

        public Cliente(string nombre, string email)
        {
            this.nombre = nombre;
            this.email = email;
        }
        private string nombre;

        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; }
        }

        private string email;

        public string Email
        {
            get { return email; }
            set { email = value; }
        }
    }

El código principal sería así:

 static void Main(string[] args)
        {
            CargarLista();

            //IList<Cliente> usuariosDeHotmail = new ClienteFinder(lista).Find(EmailSpec.HasHotmail);
            IList<Cliente> usuariosDeGmail = new ClienteFinder(lista).Find(EmailSpec.HasGmail);

            foreach (Cliente cliente in usuariosDeGmail)
            {
                Console.WriteLine("Email: {0}", cliente.Email);
            }

            Console.Read();
        }

Veamos el código de las especificaciones que se puede aplicar para obtener distintos criterios de filtrado:

public class EmailSpec
{
    public static Predicate<Cliente> HasHotmail
    {
        get{
            return delegate(Cliente cliente)
            {
                return cliente.Email.Contains("@hotmail.com");
            };

        }
    }

    public static Predicate<Cliente> HasGmail
    {
        get
        {
            return EmailSpec.MethodHasGmail;
        }
    }

    public static bool MethodHasGmail(Cliente cliente)
    {
        return cliente.Email.Contains("@gmail.com");
    }

    public static Predicate<Cliente> HasYahoo
    {
        get
        {
            return new Predicate<Cliente>(EmailSpec.MethodHasYahoo);
        }
    }

    public static bool MethodHasYahoo(Cliente cliente)
    {
        return cliente.Email.Contains("@yahoo.com");
    }

}

Aquí podemos ver, primeramente el uso de retorno de delegados usando Métodos anónimos (en HasHotmail), delegados con inferencia de tipos (en HasGmail) y la forma natural de usar un delegado (en HasYahoo). Estas son tres formas de hacer lo mismo, y yo recomiendo usar métodos anónimos que se vé en la propiedad HasHotmail y en este caso, el código se vuelve mucho más chico.

 public static Predicate<Cliente> HasHotmail
    {
        get{
            return delegate(Cliente cliente)
            {
                return cliente.Email.Contains("@hotmail.com");
            };

        }
    }

Vemos que la propiedad es de sólo lectura, y que también, retorna un predicado, que hablando mal, devolvería la función que se encargaría de la evaluación…y esa función…devolvería un booleano…true OR false, si cumple ó no. Muy prolijo no ? Sería lo mismo hacer:

  public static Predicate<Cliente> HasHotmail
    {
        get{
            return delegate(Cliente cliente)
            {
                if(cliente.Email.Contains("@hotmail.com"))
                    return true;
                else
                    return false;
            };

        }
    }

Ahora veamos el código de ClienteFinder:

public class ClienteFinder
{
    private IList<Cliente> _lista;

    public ClienteFinder(IList<Cliente> lista)
    {
        _lista = lista;
    }

    public IList<Cliente> Find(Predicate<Cliente> predicate)
    {
        List<Cliente> encontrados = new List<Cliente>();

        foreach (Cliente cliente in _lista)
        {
            if (predicate(cliente))
            {
                encontrados.Add(cliente);
            }
        }

        return encontrados;
    }

}

Lo importante acá es el constructor, que va a recibir la lista a ser filtrada. Y también el método Find, quien va a recibir el predicado correspondiente al criterio de selección. Es decir que Find puede recibir cualquiera de las tres especificaciones que preparamos: HasHotmail, HasGmail, HasYahoo, y realizar el filtrado en base a ellas.

Recursos:

1 comment so far

  1. […] leer el post de Darío llamado “Predicados y Especificaciones” y luego de hablar ayer con José quise implementar lo que Darío escribió en ese post usando lo […]


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

A %d blogueros les gusta esto: