Tim Van Wassenhove

Passionate geek, interested in Technology. Proud father of two

22 Jul 2008

About the Specification pattern

A couple of days ago i mentionned Yves Goeleven’s blog as a reference for solutions using DDD principles. Let’s have a look at his implementation of the AndSpecification and OrSpecification in Design Patterns – The Specification Pattern – Part I

class AndSpecification<T> : CompositeSpecification<T>
{
	public override bool IsSatisfiedBy(T candidate)
	{
		bool isSatisfied = true;
		foreach( ISpecification<T> spec in Specifications)
		{
			isSatisfied &= spec.IsSatisfiedBy(candidate);
		}
		return isSatisfied;
	}
}

class OrSpecification<T> : CompositeSpecification<T>
{
	public override bool IsSatisfiedBy(T obj)
	{
		bool isSatisfied = false;
		foreach( ISpecification<T> spec in Specifications)
		{
			isSatisfied |= spec.IsSatisfiedBy(obj);
		}
		return isSatisfied;
	}
}

I believe that it would be better if the implementations had the same lazy evaluation behaviour as the C# && and || operators. Eg: in C# one can write (a && b) and if a evaluates to false, then b is not evaluated anymore. Consider the following code

if (person != null && person.Age > 18) { }

Let’s rewrite that same code using Specifications

class NotNullSpecification<T> : ISpecification<T> where T : class
{
	public bool IsSatisfiedBy(T item)
	{
		return item != null;
	}
}

class OlderThanSpecification : ISpecification<person> {
	private int age;

	public OlderThanSpecification(int age)
	{
		this.age = age;
	}

	public bool IsSatisfiedBy(Person person)
	{
		return person.Age > this.age;
	}
}

class PersonNotNullAndOlderThan18Specification : AndSpecification<person> 
{
	public PersonNotNullAndOlderThan18Specification()
	: base(new NotNullSpecification<person>(), new OlderThanSpecification(18))
	{
	}
}

Using the mentionned AndSpecification implementation the following unittest will fail

[TestMethod]
public void TestPersonNotNullAndOlderThan18Specification()
{
	Person person = null;

	ISpecification<person> specification = new PersonNotNullAndOlderThan18Specifictation();
	Assert.IsFalse(specification.IsSatisfiedBy(person));
}

In order to make that test pass we could rewrite the specifications as following

public abstract class CompositeSpecification<T> : ISpecification<T>
{
	private readonly IEnumerable<ispecification<T>> specifications;

	public CompositeSpecification(IEnumerable<ispecification<T>> specifications)
	{
		this.specifications = specifications;
	}

	protected IEnumerable<ispecification<T>> Specifications
	{
		get { return this.specifications; }
	}

	abstract public bool IsSatisfiedBy(T item);
}

public class AndSpecification<T> : CompositeSpecification<T>
{
	public AndSpecification(IEnumerable<ispecification<T>> specifications)
	:base(specifications)
	{ }

	override public bool IsSatisfiedBy(T item)
	{
		foreach (ISpecification<T> specification in this.Specifications)
		{
			if (!specification.IsSatisfiedBy(item))
			{
			return false;
			}
		}

		return true;
	}
}

public class OrSpecification<T> : CompositeSpecification<T>
{
	public OrSpecification(IEnumerable<ispecification<T>> specifications)
	: base(specifications)
	{ }

	override public bool IsSatisfiedBy(T item)
	{
		foreach (ISpecification<T> specification in this.Specifications)
		{
			if (specification.IsSatisfiedBy(item))
			{
				return true;
			}
		}

		return false;
	}
}