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.

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();