This week, I hit an interesting problem which I don’t feel like was solved in the best possible way. The problem was that we needed to filter a list of objects based on some known criteria. Testing the specification is pretty important as there are a series of and’ed negating conditions (eg: IF this AND NOT that AND NOT the other etc…), in total about 5 unique criteria for the one filter.

Ordinarily this kind of implementation would lend itself nicely to the Specification Pattern given that all information required to determine if the specification is satisfied exists on the object member being passed in at the time of evaluation. In my case, however, I had 3 conditions that the object being evaluated must not exist in 3 different lists. To give you an example the object under evaluation is a model, and this step of filtering is part of a much larger process involving running the model through a recursive algorithm. At each step of the algorithm, the model object could have

  • Run through the algorithm succesfully
  • Been aborted during execution of the algorithm
  • Still awaiting to be executed through the algorithm

These three states are tracked by keeping 3 lists – one for each criteria. The filter that I was working on had to filter based on whether the model under evaluation was NOT in those three lists. I realise the wordiness of me explaining this doesn’t really clear the air, so lets look at some code (with relevant types changed).

This is my model:

    public class User
       public bool Enabled { get; set; }
       public string Name { get; set; }
       public UserType TypeOfUser { get; set; }

The code which uses my filter looks something like this. This guy will be called recursively based on the result that gets returned from here. In this case, i’m building the allValidUsersFilter.

public class ReplacementUserFinder

        public User FindReplacementUser(User userToReplace, IList<User> allPossibleUsers, IList<User> usersStillToBeEvaluated, IList<User> usersAlreadyEvaluated, IList<User> usersAbortedDuringEvaluation)
            var validUsers = _allValidUsersFilter.Filter(allPossibleUsers, usersStillToBeEvaluated, usersAlreadyEvaluated, usersAbortedDuringEvaluation);
            var replacementUser = _bestReplacementCandidateFinder.Find(validUsers, userToReplace);

            return replacementUser;

and the interface for the AllValidUsersFilter – it’s purpose is to filter the list of all users to a list of potential candidates given the list of all users:

        public IList<User> Filter(IList<User> allpossibleUsers, IList<User> usersStillToBeEvaluated, IList<User> usersAlreadyEvaluated, IList<User> usersAbortedDuringEvaluation)
            return allpossibleUsers.Where(x => 
                _isUserEnabledSpecification.IsSatisfiedBy(x) &&
                _isOverseasUserSpecification.IsSatisfiedBy(x) &&
                    !_isUserStillToBeEvaluatedSpecification.IsSatisfiedBy(x, usersStillToBeEvaluated) &&
                    !_isUserAlreadyEvaluatedSpecification.IsSatisfiedBy(x, usersAlreadyEvaluated) &&
                    !_isUserAbortedDuringEvaluationSpecification.IsSatisfiedBy(x, usersAbortedDuringEvaluation)

The specification instances here are being ctor injected into my filter’s instance so that I can use a behavioural style assertion to check that the specification is invoked correctly by the filter.

The IsUserEnabledSpecification, and IsOverseasUserSpecification just use the well-known ISpecification interface pattern, but in order to evaluate the the other three, I had to create an IListSpecification and it feels somehow unsatisfying because the only thing different between them is that I have to pass in the list to the IsSatisfiedBy methods.

I’m not happy with this result, and we went through several different options before settling on this one purely just so we could move forward, and come back to address this later.

Hoping someone out there might have some suggestions…After writing this post, i’ve come up with another idea which would probably be cleaner…need to try it out.