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: