Patrón Criteria (patrón de diseño)

De Wikipedia, la enciclopedia libre

En diseño de software, el patrón de especificación (y en su caso particular, criteria o filter) es un patrón de diseño, mediante el cual, se permite filtrar una colección de objetos bajo diversos criterios, encadenándolos de una manera desacoplada por medio de operaciones lógicas. Este patrón se utiliza en escenarios específicos, donde la obtención de uno o más entidades depende de reglas de negocio. Fue creado por Eric Evans y Martin Fowler[1]

Propósito[editar]

Filtrar colecciones de objetos según diversos criterios de forma desacoplada. Permitir la reutilización y anidación de criterios por medio de operaciones lógicas (and, or, not). Generar una manera legible y extensible de agregar o quitar lógica para filtrar colecciones de objetos.

Motivación[editar]

Frecuentemente, se necesita filtrar colecciones de objetos de la misma familia (clase base) utilizando criterios similares, pero en distinto orden y/o condición. También a menudo, esta lógica para filtrar colecciones es un proceso clave o complejo como pueden ser los criterios de planificación en un Sistema Operativo o el manejo de prioridades para una cola de peticiones. Otra caso sería el tener altas posibilidades de cambio en el futuro sobre la lógica que realiza el filtrado en esta colección de objetos. Sea quitando/modificando criterios existentes, como así también agregando nuevos. Este patrón pone gran énfasis en la extensibilidad y reutilización de criterios, como así también en la legibilidad del código.

Ventajas[editar]

  • Extensibilidad
  • Legibilidad
  • Reutilización de criterios
  • Facilidad para hacer test unitarios (cada criterio se puede probar independiente, el patrón asegura el uso colectivo)
  • Activación de criterios en tiempo de ejecución (dependiendo del lenguaje de programación)

Estructura[editar]

Estructura básica del patrón independiente del lenguaje de programación.

Estructura .NET[editar]

Aquí se presenta para .NET, utilizando métodos de extensión

Implementación (C#)[editar]

public interface ICriteria<E>
{
   List<E> MeetCriteria(List<E> entities); 
}

internal class AndCriteria<E> : ICriteria<E>
{
   private ICriteria<E> _criteria;
   private ICriteria<E> _otherCriteria;
  
   internal AndCriteria(ICriteria<E> criteria, ICriteria<E> otherCriteria)
   {
      _criteria = criteria;
      _otherCriteria = otherCriteria;
   }

   public List<E> MeetCriteria(List<E> entities)
   {
      var result = _criteria.MeetCriteria(entities);
      // Si ya devuelve 1, es que uno solo cumplio el criterio y no
      // se ejecutan los ands anidados
      // Si ya devuelve 0, es que ninguno cumplio con el criterio y
      // no se ejecutan los ands anidados
      if (result.Count == 0 || result.Count == 1)
         return result;
      
      return _otherCriteria.MeetCriteria(result);
   }
}

internal class OrCriteria<E> : ICriteria<E>
{
   private ICriteria<E> _criteria;
   private ICriteria<E> _otherCriteria;

   internal OrCriteria(ICriteria<E> criteria, ICriteria<E> otherCriteria)
   {
      _criteria = criteria;
      _otherCriteria = otherCriteria;
   }

   public List<E> MeetCriteria(List<E> entities)
   {
      List<E> firstCriteriaItems = _criteria.MeetCriteria(entities);
      List<E> otherCriteriaItems = _otherCriteria.MeetCriteria(entities);
    
      foreach (E otherCriteriaItem in otherCriteriaItems)
      {
         if(!firstCriteriaItems.Contains(otherCriteriaItem))
            firstCriteriaItems.Add(otherCriteriaItem);
      }
     
      return firstCriteriaItems;
   }
}

internal class NotCriteria<E> : ICriteria<E>
{
   private ICriteria<E> _criteria;

   internal NotCriteria(ICriteria<E> x)
   {
      _criteria = x;
   }

   public List<E> MeetCriteria(List<E> entities)
   {
      List<E> notCriteriaItems = _criteria.MeetCriteria(entities);
  
      foreach (E notCriteriaItem in notCriteriaItems)
         entities.Remove(notCriteriaItem);
 
      return entities;
   }
}

public static class CriteriaExtensionMethods
{
   public static ICriteria<E> And<E>(this ICriteria<E> criteria, ICriteria<E> otherCriteria)
   {
      return new AndCriteria<E>(criteria, otherCriteria);
   }

   public static ICriteria<E> Or<E>(this ICriteria<E> criteria, ICriteria<E> otherCriteria)
   {
      return new OrCriteria<E>(criteria, otherCriteria);
   }

   public static ICriteria<E> Not<E>(this ICriteria<E> criteria)
   {
      return new NotCriteria<E>(criteria);
   }
}

Ejemplo de uso[editar]

Suponiendo la existencia de la clase Persona

public class Persona
{
   public int Edad { get; set; }
   public string Nombre { get; set; }
   public string Descripcion { get; set; }
   public string Apellido { get; set; }
   public Sexo Sexo { get; set; }
   public Origen Origen { get; set; }
   public EstadoMarital EstadoMarital { get; set; }
   public Profesion Profesion { get; set; }
}

Y creando estos criterios:

public class CriterioMasculinos : ICriteria<Persona>
    {
        public List<Persona> MeetCriteria(List<Persona> entities)
        {
            var hombres =   from h in entities
                            where h.Sexo == Sexo.Masculino
                            select h;

            return hombres.ToList();
        }
    }

    public class CriterioFemeninos : ICriteria<Persona>
    {
        public List<Persona> MeetCriteria(List<Persona> entities)
        {
            var mujeres =   from m in entities
                            where m.Sexo == Sexo.Femenino
                            select m;

            return mujeres.ToList();
        }
    }
    

    public class CriterioExtranjeros : ICriteria<Persona>
    {
        public List<Persona> MeetCriteria(List<Persona> entities)
        {
            var personas = from h in entities
                          where h.Origen == Origen.Extranjero
                          select h;

            return personas.ToList();
        }
    }

    public class CriterioSolteros : ICriteria<Persona>
    {
        public List<Persona> MeetCriteria(List<Persona> entities)
        {
            var personas = from h in entities
                           where h.EstadoMarital == EstadoMarital.Soltero
                           select h;

            return personas.ToList();
        }
    }

Podríamos filtrar a los hombres extranjeros y solteros

ICriteria<Persona> masculino = new CriterioMasculinos();
ICriteria<Persona> femenino = new CriterioFemeninos();
ICriteria<Persona> soltero = new CriterioSolteros();
ICriteria<Persona> extranjero = new CriterioExtranjeros();

/* ---------- HOMBRES EXTRANJEROS Y SOLTEROS ---------- */
criterios =  masculino.And(extranjero).And(soltero);

foreach (var persona in criterios.MeetCriteria(personas))
   Console.WriteLine(persona.Descripcion);

Console.ReadLine();

O a las mujeres extranjeras

ICriteria<Persona> masculino = new CriterioMasculinos();
ICriteria<Persona> femenino = new CriterioFemeninos();
ICriteria<Persona> soltero = new CriterioSolteros();
ICriteria<Persona> extranjero = new CriterioExtranjeros();

/* ---------- MUJERES EXTRANJERAS ---------- */
criterios = femenino.And(extranjero); // esto seria lo mismo que masculino.Not().And(extranjero)

foreach (var persona in criterios.MeetCriteria(personas))
   Console.WriteLine(persona.Descripcion);

Console.ReadLine();

O también podríamos filtrar a los hombres o mujeres (a todos)

ICriteria<Persona> masculino = new CriterioMasculinos();
ICriteria<Persona> femenino = new CriterioFemeninos();
ICriteria<Persona> soltero = new CriterioSolteros();
ICriteria<Persona> extranjero = new CriterioExtranjeros();

/* ---------- HOMBRES O MUJERES ---------- */
criterios = masculino.Or(femenino);

foreach (var persona in criterios.MeetCriteria(personas))
   Console.WriteLine(persona.Descripcion);

Console.ReadLine();

Referencias[editar]

  1. Specifications by Eric Evans and Martin Fowler

Enlaces externos[editar]