this is a blog by Jeff Perrin, a software developer based in calgary alberta

Specification Pattern With Predicates

I've been sick the last two days (I got a cold during the hottest part of the year so far, go figure) so I had some time to play around with C# 2.0's generics implementation. What originally got me started was having an old project lying around that had out of date subversion information. Rather than going through each and every sub-directory and deleting .svn directories, I figured I'd write a little one-off app to do it for me. I started out by attempting to implement the specification pattern, which led to various attempts at generic implementations that were never quite right. Fortunately, while searching for information on generics I stumbled across Joe Walnes post on The power of closures in C# 2.0 which details the use of predicate generic delegates.

For those not familiar with the specification pattern, it basically just allows you to filter a list of objects while keeping your condition separate from your loop. As an example, we often see something like this:

    1 public IList FindAllMaleCustomers()

    2         {

    3             IList matches = new ArrayList();

    4             foreach(Customer customer in Customers)

    5             {

    6                 if(customer.IsMale)

    7                 {

    8                     matches.Add(customer);

    9                 }

   10             }

   11             return matches;

   12         }

The specification pattern removes that customer.IsMale check out into a specification object, so we can now write something like this instead:

   42 public IList FindAllMaleCustomers()

   43         {

   44             return new CustomerFinder(Customers).FindAll(new MaleCustomerSpecification());

   45         }

Therefore, if the definition of a "Male" ever changes (bad example, I know) we can just change our MaleCustomerSpecification object.

Fortunately for me, .NET 2.0 includes a new type of delegate called the predicate generic delegate, which

Represents the method that defines a set of criteria and determines whether the specified object meets those criteria.

Seems like it's exactly what we need. So rather than writing a specific specification object, we can just write a generic predicate like so:

    1     public class Spec

    2     {

    3         public static Predicate<DirectoryInfo> SubversionDirectory

    4         {

    5             get

    6             {

    7                 return delegate(DirectoryInfo dirInfo)

    8                           {

    9                               return dirInfo.Name == ".svn";

   10                           };

   11             }

   12         }

   13     }

Which we then use like this:

    1 new DirectoryFinder(directories).FindAll(Spec.SubversionDirectory);

Our DirectoryFinder object is a little more complex than a standard FindAll on a method like System.Collections.Generic.List would be, because we recurse all the way through the sub-directories of a given directory:

    1 public class DirectoryFinder

    2     {

    3         private IList<DirectoryInfo> _directories;


    5         public DirectoryFinder(IList<DirectoryInfo> directories)

    6         {

    7             _directories = directories;

    8         }


   10         public DirectoryFinder(DirectoryInfo directory)

   11         {

   12             _directories = directory.GetDirectories();

   13         }


   15         public IList<DirectoryInfo> FindAll(Predicate<DirectoryInfo> predicate)

   16         {

   17             return FindAll(predicate, _directories);

   18         }


   20         private IList<DirectoryInfo> FindAll(Predicate<DirectoryInfo> predicate, IList<DirectoryInfo> directories)

   21         {

   22             List<DirectoryInfo> matches = new List<DirectoryInfo>();

   23             foreach (DirectoryInfo info in directories)

   24             {

   25                 if (predicate(info))

   26                 {

   27                     matches.Add(info);

   28                 }

   29                 else

   30                 {

   31                     matches.AddRange(FindAll(predicate, info.GetDirectories()));

   32                 }

   33             }

   34             return matches;

   35         }

   36     }

I'm almost certain there's other ways of doing this, some are probably better as well. For another use of predicates, check out Jean-Paul Boodhoo's post on validation in the domain layer, where he uses them to flesh out business rules.

Working on a Large Agile Project

Stuff That Sucks #1