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:
- El código completo usando predicados se puede encontrar aquí.
- El código sin predicados, de la manera silvestre está aquí.
- Para los que leen VB.Net aquí les vá…no pidan Metodos anónimos.
- Sigo recomendando este artículo: Create Elegant Code With Anonymous Methods, Iterators, And Partial Classes
- Post anterior sobre: Delegados, Eventos y Métodos anónimos
- Y en esté artículo, se explica el porqué, los métodos anónimos C#, no son closures, soportadas nativamente por Ruby. Pero esto es tema para otros post, pero les dejo la inquietud.
2 Responses to Predicados y especificaciones
Leave a Reply Cancel reply
-
Categories
- .NET
- ActiveRecord
- agil
- Alt.Net
- AOP
- Asp.net MVC
- blog
- C#
- codegeneration
- cosas interesantes
- db4o
- db4o-analyzer
- DLR
- enterprise-library
- expresiones regulares
- General
- Hibernate
- IoC
- it
- Java
- LINQ
- Linux
- Lucene
- Mocking
- Mono
- monosms
- MVC
- NAnt
- NHibernate
- NHibernate.Burrow
- NHibernate.Contrib
- NHibernate.Validator
- noticias
- O/R Mapping
- Open Source
- patrones
- personal
- podcast
- programación
- Redes Neuronales
- Refactoring
- regex
- Scrum
- Security
- Shards
- Sin Categoría
- Spring
- SqlServer
- Testing
- tips
- Tool
- tutorial
- Ubuntu
- uNHAddIns
- video
- Virtualización
- Visual Basic.Net
- Visual Studio
- VMWare
- Web
-
Articles
- June 2011
- May 2011
- November 2009
- October 2009
- April 2009
- March 2009
- February 2009
- October 2008
- September 2008
- August 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- January 2007
- December 2006
- November 2006
- October 2006
- September 2006
- August 2006
- March 2006
- February 2006
- December 2005
- November 2005
- October 2005
-
Meta





Muy lindo post Dario, lo recomendé en la lista de Cooperator
Un abrazo,
Eugenio
Eugenio ! Como vá todo…
Que bien, espero que ayude.
Un abrazo.