THE SPECIFICATION PATTERN
Download the sample C# code for the Specification Pattern.
Background
I came across this clever and interesting pattern definition when researching alternatives for a business rules processing component of a database system.
The basic principle of the pattern is to define a specification for an object which encapsulates a set of conditions that the object must fulfil in order for it to satisfy the specification. The specification, represented itself as an object, is then used to identify whether a given real-life object satisfies that specification.
Implementations of the specification pattern include basic decision and business rules processing engines. Hence my interest in the pattern! However, in my research I couldn’t find a decent C# or Java example implementation of the pattern, so I thought I’d put one together for all to see (and criticise!).
I don’t want to cover the definition of the Specification Pattern in detail here. Martin Fowler and Eric Evans have full documentation for the pattern in their article on the subject located on Martin Fowler's site.
Pattern Definition
| Problem | You need to identify a set of objects based on some conditions that those objects meet. |
| Solution | "Create a specification that is able to tell if a candidate object matches some criteria. The specification has a method isSatisfiedBy(anObject) : Boolean that returns true if all criteria are met by an object." [Fowler & Evans] |
| Consequences |
|
Why is this applicable to a business rules processing system?
Specifications comprise a set of conditions that a candidate object must fulfill in order to meet the specification. Fowler & Evans illustrate three situations in which a specification is applicable in their pattern documentation:
A business rule is a condition or set of conditions that you test candidate objects against to see if they pass the rule. A business rule can be seen as a specification that candidate objects must satisfy.
Lets have a look at an example business rule. Say you are writing a system that needs to identify whether a given set of candidate "Car" objects are in fact Cars. A Car must have at least three wheels (not two or less), and an engine. Both of these rules can be expressed as boolean conditions, as follows:
In the specification pattern, the boolean conditions above are expressed in terms of specification objects. In this case, two specifications could exist, one which specifies that a candidate object must have 2 or more wheels, and another that specifies that a candidate object has an engine. The system would then use these specifications to check candidate objects to see if those specifications (rules) are met.
Class Design
The following UML class model illustrates how these specifications may be designed. RuleSpecification is an abstract type that all rules derive from. The two concrete specifications "Has2OrMoreWheelsSpecification" and "HasEngineSpecification" are specifications that test a candidate object to identify whether it has 2 or more wheels and an engine (respectively):
UML Class model of a Car specification
The addition of the Add() and Or() operations to the RuleSpecification abstract super type means that different specifications can be chained together so that a given candidate object can be tested against a set of specifications (or rules). In terms of a business rules engine, this chain of specifications can be considered a rule set.
Implementation
The following C# code illustrates how this class design could be implemented. To download a sample C# implementation, see my code sample.
namespace RulesEngine
{
public abstract class RuleSpecification
{
private RuleSpecification _andSpec = null;
private RuleSpecification _orSpec = null;
public abstract bool IsSatisfiedBy(Object obj);
protected bool Return(Object obj, bool rtn)
{
if (_andSpec != null) { rtn &= _andSpec.IsSatisfiedBy(obj); }
if (_orSpec != null) { rtn |= _orSpec.IsSatisfiedBy(obj); }
return rtn;
}
public void And(RuleSpecification spec)
{
_andSpec = spec;
}
public void Or(RuleSpecification spec)
{
_orSpec = spec;
}
}
public class Has2OrMoreWheelsSpecification : RuleSpecification
{
public override bool IsSatisfiedBy(object obj)
{
bool rtn = ((obj.GetType() == typeof(Car)) && (((Car)obj).NumberOfWheels > 2));
return base.Return(obj, rtn);
}
}
public class HasEngineSpecification : RuleSpecification
{
public override bool IsSatisfiedBy(object obj)
{
bool rtn = ((obj.GetType() == typeof(Car)) && (((Car)obj).HasEngine));
return base.Return(obj, rtn);
}
}
}
Client code might then use these classes as follows:
RuleSpecification wheelsSpec = new Has2OrMoreWheelsSpecification();
RuleSpecification engineSpec = new HasEngineSpecification();
wheelsSpec.And(engineSpec);
wheelsSpec.IsSatisfiedBy(candidateObject);
Processing Precedence
Notice that the implementation of the Return() operation on the RuleSpecification class performs an AND of any _andSpec
instance prior to its OR of any _orSpec instance. This is in order to set the processing precedence to ANDing before ORing.
However, lets say you have three conditions. The first is AND'd with the second; and the second OR'd with the third. There are two orders of processing precedence that you could follow, each of which will yeild different results:
RuleSpecification condition1Spec = new Condition1Specification();
RuleSpecification condition2Spec = new Condition2Specification();
RuleSpecification condition3Spec = new Condition3Specification();
condition1Spec.And(condition2Spec);
condition1Spec.Or(condition3Spec);
condition1Spec.IsSatisfiedBy(candidateObject);
You can see in the sample above that it's condition1Spec that's doing the ANDing and the ORing. This means that the processing precedence set in
the Return() operation implementation in the RuleSpecification class is used. Now, lets say you wanted to implement the second processing
ordering:
RuleSpecification condition1Spec = new Condition1Specification();
RuleSpecification condition2Spec = new Condition2Specification();
RuleSpecification condition3Spec = new Condition3Specification();
condition1Spec.And(condition2Spec);
condition2Spec.Or(condition3Spec);
condition1Spec.IsSatisfiedBy(candidateObject);
Notice now that condition2Spec is the instance that is doing the ORing. This means that condidtion2 and condition3 are OR'd and then condition1 is AND'd with their result.