Basic contract and abstract implementation of the specification pattern.
Specimen
can be installed using the NuGet command line or the NuGet Package Manager in Visual Studio.
PM> Install-Package Specimen
First, you will need to add the following using statement:
using Specimen;
To create your own specification classes, there are 2 options:
- Implement the
ISpecification<T>
interface:
public class PositiveIntegerSpecification : ISpecification<int>
{
public Expression<Func<int, bool>> Predicate => value => value > 0;
public bool IsSatisfiedBy(int value)
{
// Ideally the compiled expression should be cached
var compiled = this.Predicate.Compile();
return compiled(value);
}
}
public class GreaterThanSpecification : ISpecification<int>
{
private readonly int threshold int;
public LargerThanSpecification(int threshold)
{
this.threshold = threshold;
}
public Expression<Func<int, bool>> Predicate => value => value > this.threshold;
public bool IsSatisfiedBy(int value)
{
// Ideally the compiled expression should be cached
var compiled = this.Predicate.Compile();
return compiled(value);
}
}
public class MultipleOfSpecification : ISpecification<int>
{
private readonly int baseNumber int;
public MultipleOfSpecification(int baseNumber)
{
this.baseNumber = baseNumber;
}
public Expression<Func<int, bool>> Predicate => value => value % this.baseNumber == 0;
public bool IsSatisfiedBy(int value)
{
// Ideally the compiled expression should be cached
var compiled = this.Predicate.Compile();
return compiled(value);
}
}
- Extend the
SpecificationBase<T>
abstract class:
public class PositiveIntegerSpecification : SpecificationBase<int>
{
public override Expression<Func<int, bool>> Predicate => value => value > 0;
}
public class GreaterThanSpecification : SpecificationBase<int>
{
private readonly int threshold int;
public LargerThanSpecification(int threshold)
{
this.threshold = threshold;
}
public override Expression<Func<int, bool>> Predicate => value => value > this.threshold;
}
public class MultipleOfSpecification : ISpecification<int>
{
private readonly int baseNumber int;
public MultipleOfSpecification(int baseNumber)
{
this.baseNumber = baseNumber;
}
public override Expression<Func<int, bool>> Predicate => value => value % this.baseNumber == 0;
}
The SpecificationBase<T>
class already implements the ISpecification<T>#IsSatisfiedBy
method in a cache-aware manner.
Logical operations like negation, conjunction (AND) and disjunction (OR) can be applied on ISpecification<T>
instances as follows:
var positive = new PositiveIntegerSpecification();
var negativeOrZero = positive.Negate();
Assert(negativeOrZero.IsSatisfiedBy(-10));
Assert(negativeOrZero.IsSatisfiedBy(-5));
Assert(negativeOrZero.IsSatisfiedBy(0));
Assert(negativeOrZero.IsSatisfiedBy(5)); // Raises error
var positive = new PositiveIntegerSpecification();
var multipleOfTwo = new MultipleOfSpecification(2);
var largerThanFive = new GreaterThanSpecification(5);
var positiveMultipleOfTwoLargerThanFive = positive.And(multipleOfTwo, largerThanFive);
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(-10)); // Raises error
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(0)); // Raises error
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(15)); // Raises error
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(20));
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(25)); // Raises error
var positive = new PositiveIntegerSpecification();
var multipleOfTwo = new MultipleOfSpecification(2);
var largerThanFive = new GreaterThanSpecification(5);
var positiveMultipleOfTwoLargerThanFive = positive.Or(multipleOfTwo, largerThanFive);
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(-10)); // Raises error
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(0)); // Raises error
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(15));
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(20));
Assert(positiveMultipleOfTwoLargerThanFive.IsSatisfiedBy(25));
Support for LINQ expressions/functions is provided by the library, allowing to use ISpecification<T>
instances as inpuf of Where
invocations:
var source = new []{-10, 5, 0, -5, 20 };
var positive = new PositiveIntegerSpecification();
var filtered = source.Where(positive); // Yields [5, 20]
Assert(positiveMultipleOfTwoLargerThanFive.Count() == 2);
Check out some of my other C# projects:
- DotNetFunctional.Maybe: An Option type monad for C# with LINQ support and rich fluent syntax.
- DotNetFunctional.Try: The Try monad (Error/Exceptional monad) for C# with LINQ support and rich fluent syntax.