Criteria Pattern

From Wikipedia, the free encyclopedia
Jump to: navigation, search

In software design the Criteria design pattern, 'aka' Filter, is a design pattern that enables you to filter a set of objects, using different criteria, chaining them in a decoupled way through logical operations. This pattern is used in specific scenarios where the extract of one or more entities depends on the business needs. This pattern was created by Andrés Grosso.

Purpose[edit]

Filter a set of objects, using different criteria, chaining them in a decoupled way. Allow criterion reutilization and inheritance through logical operations (and, or, not). Generate a legible and extendable way of adding or removing logics to filter sets of objects.

Motive[edit]

We frequently need to filter sets of objects of the same family (base class) using similar criteria but different order and/or conditions. Usually, the logic being used for filter is a key process or a complex process - like the planning criterion in an Operating System or the priority management in a request queue can be. Another reason would be to be able to change in the logics used for the object filtering, in the future, either by adding/modifying existing criterion or by adding new criterion This pattern places great emphasis in the extent and reutilization of criterion as well as in the code legibility.

Pros[edit]

  • Extensibility
  • Legibility
  • Criterion reutilization
  • Ease of doing unit tests (each criterion can be tested independently, the pattern ensures collective use
  • Enabling run-time criteria (depending on the programming language)

Structure[edit]

Pattern basic structure (regardless of the programming language).

Patron criteria general.png

.NET Structure[edit]

This is the .NET structure, using extension methods

Patron criteria csharp.png

Implementation (C#)[edit]

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);
      // If it returns 1 is because only 1 met the criterion
      // and the inherited ands are not executed
      // If it returns 0 is because none met the criterion
      // and the inherited ands are not executed
 
      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);
   }
}

Usage[edit]

Assuming the existence of class Person

public class Person
{
   public int Age { get; set; }
   public string Name { get; set; }
   public string Description { get; set; }
   public string LastName { get; set; }
   public Gender Gender { get; set; }
   public Nationality Nationality { get; set; }
   public MaritalStatus MaritalStatus  { get; set; }
   public Occupation  Occupation { get; set; }
}

And creating the below criterion

public class CriterionMales : ICriteria<Person>
    {
        public List<Person> MeetCriteria(List<Person> entities)
        {
            var men  =   from h in entities
                            where h.Gender == Gender.Male
                            select h;
 
            return men .ToList();
        }
    }
 
    public class CriterionFemales : ICriteria<Person>
    {
        public List<Person> MeetCriteria(List<Person> entities)
        {
            var women  =   from m in entities
                            where m.Gender == Gender .Female
                            select m;
 
            return women .ToList();
        }
    }
 
    public class CriterionForeigners : ICriteria<Person>
    {
        public List<Person> MeetCriteria(List<Person> entities)
        {
            var persons = from h in entities
                          where h.Nationality == Nationality .Foreigner
                          select h;
 
            return persons.ToList();
        }
    }
 
    public class CriterionSingles : ICriteria<Person>
    {
        public List<Person> MeetCriteria(List<Person> entities)
        {
            var persons = from h in entities
                           where h.MaritalStatus  == MaritalStatus .Single
                           select h;
 
            return persons .ToList();
        }
    }

We could filter single, foreigner man.

ICriteria<Person> male = new CriterionMale();
ICriteria<Person> female = new CriterionFemale();
ICriteria<Person> single = new CriterionSingle();
ICriteria<Person> foreigner = new CriterionForeigner();
 
/* ---------- SINGLE FOREIGNER MEN ---------- */
criterion =  male.And(foreigner).And(single);
 
foreach (var person in criterion.MeetCriteria(persons))
   Console.WriteLine(person.Description);
 
Console.ReadLine();

Or foreigner woman

ICriteria<Person> male = new CriterionMales();
ICriteria<Person> female = new CriterionFemales();
ICriteria<Person> single = new CriterionSingles();
ICriteria<Person> foreigner = new CriterionForeigners();
 
/* ---------- FOREIGNER WOMEN ---------- */
criterion = female.And(foreigner); // this would be the same as male.Not().And(foreigner)
 
foreach (var person in criterion.MeetCriteria(persons))
   Console.WriteLine(person.Description);
 
Console.ReadLine();

Or we could also filter men and women (all of them)

ICriteria<Person> male = new CriterionMales();
ICriteria<Person> female = new CriterionFemales();
ICriteria<Person> single = new CriterionSolteros();
ICriteria<Person> foreigner = new CriterionExtranjeros();
 
/* ---------- MEN OR WOMEN ---------- */
criterion = male.Or(female);
 
foreach (var person in criterion.MeetCriteria(persons))
   Console.WriteLine(person.Description);
 
Console.ReadLine();

Referencias[edit]

Enlaces externos[edit]