Specification is a pattern (Evans/Fowler) that can be used to apply rules for your domain entities:
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 anObject.
It defines an interface like
public interface ISpecification
{
bool IsSatisfiedBy<T>(T candidate);
ISpecification And(ISpecification other);
ISpecification Or(ISpecification other);
ISpecification Not();
}
where IsSatisfiedBy() wraps the business logic (not contained in the entity as OO would suggest).
That allows to write business logic code like
price.And(seats).And(slow.Not())
My car specification:
public class CarSpecification : Specifications.CompositeSpecification
{
public Func<Car, bool> IsSatisfiedFunction { get; set; }
public CarSpecification(Func<Car, bool> isSatisfiedFunction)
{
this.IsSatisfiedFunction = isSatisfiedFunction;
}
public override bool IsSatisfiedBy<T>(T candidate)
{
if (!(candidate is Car))
{
throw new ArgumentException("must pass Car entity", "candidate");
}
if (this.IsSatisfiedFunction != null)
{
return this.IsSatisfiedFunction(candidate as Car);
}
return false;
}
}
The whole code:
public partial class MainWindow : Window
{
private IList<Car> cars;
private CarSpecification price = new CarSpecification(c => c.Price <= 40000);
private CarSpecification seats = new CarSpecification(c => c.Seats >= 7);
private CarSpecification slow = new CarSpecification(c => c.HorsePower < 140);
public MainWindow()
{
InitializeComponent();
this.cars = ApplyRules(Cars.Get());
this.DataContext = this.cars;
}
private IList<Car> ApplyRules(IList<Car> cars)
{
ISpecification myCarSpecification = price.And(seats).And(slow.Not());
return cars.Where(c => myCarSpecification.IsSatisfiedBy<Car>(c)).ToList();
}
}
Definitively this simple example could be written in other ways. More importantly you evaluate your new car ….