Skip to content

Instantly share code, notes, and snippets.

@thdotnet
Created November 23, 2021 22:47
Show Gist options
  • Save thdotnet/aad40ee7211f4d0ac2c189f963bee0f9 to your computer and use it in GitHub Desktop.
Save thdotnet/aad40ee7211f4d0ac2c189f963bee0f9 to your computer and use it in GitHub Desktop.
specification pattern
using System;
using System.Linq;
using System.Linq.Expressions;
namespace Logic.Movies
{
internal sealed class IdentitySpecification<T> : Specification<T>
{
public override Expression<Func<T, bool>> ToExpression()
{
return x => true;
}
}
public abstract class Specification<T>
{
public static readonly Specification<T> All = new IdentitySpecification<T>();
public bool IsSatisfiedBy(T entity)
{
Func<T, bool> predicate = ToExpression().Compile();
return predicate(entity);
}
public abstract Expression<Func<T, bool>> ToExpression();
public Specification<T> And(Specification<T> specification)
{
if (this == All)
return specification;
if (specification == All)
return this;
return new AndSpecification<T>(this, specification);
}
public Specification<T> Or(Specification<T> specification)
{
if (this == All || specification == All)
return All;
return new OrSpecification<T>(this, specification);
}
public Specification<T> Not()
{
return new NotSpecification<T>(this);
}
public static Specification<T> operator &(Specification<T> lhs, Specification<T> rhs) => lhs.And(rhs);
public static Specification<T> operator |(Specification<T> lhs, Specification<T> rhs) => lhs.Or(rhs);
public static Specification<T> operator !(Specification<T> spec) => spec.Not();
}
internal sealed class AndSpecification<T> : Specification<T>
{
private readonly Specification<T> _left;
private readonly Specification<T> _right;
public AndSpecification(Specification<T> left, Specification<T> right)
{
_right = right;
_left = left;
}
public override Expression<Func<T, bool>> ToExpression()
{
Expression<Func<T, bool>> leftExpression = _left.ToExpression();
Expression<Func<T, bool>> rightExpression = _right.ToExpression();
var invokedExpression = Expression.Invoke(rightExpression, leftExpression.Parameters);
return (Expression<Func<T, Boolean>>)Expression.Lambda(Expression.AndAlso(leftExpression.Body, invokedExpression), leftExpression.Parameters);
}
}
internal sealed class OrSpecification<T> : Specification<T>
{
private readonly Specification<T> _left;
private readonly Specification<T> _right;
public OrSpecification(Specification<T> left, Specification<T> right)
{
_right = right;
_left = left;
}
public override Expression<Func<T, bool>> ToExpression()
{
Expression<Func<T, bool>> leftExpression = _left.ToExpression();
Expression<Func<T, bool>> rightExpression = _right.ToExpression();
var invokedExpression = Expression.Invoke(rightExpression, leftExpression.Parameters);
return (Expression<Func<T, Boolean>>)Expression.Lambda(Expression.OrElse(leftExpression.Body, invokedExpression), leftExpression.Parameters);
}
}
internal sealed class NotSpecification<T> : Specification<T>
{
private readonly Specification<T> _specification;
public NotSpecification(Specification<T> specification)
{
_specification = specification;
}
public override Expression<Func<T, bool>> ToExpression()
{
Expression<Func<T, bool>> expression = _specification.ToExpression();
UnaryExpression notExpression = Expression.Not(expression.Body);
return Expression.Lambda<Func<T, bool>>(notExpression, expression.Parameters.Single());
}
}
public sealed class MovieForKidsSpecification : Specification<Movie>
{
public override Expression<Func<Movie, bool>> ToExpression()
{
return movie => movie.MpaaRating <= MpaaRating.PG;
}
}
public sealed class AvailableOnCDSpecification : Specification<Movie>
{
private const int MonthsBeforeDVDIsOut = 6;
public override Expression<Func<Movie, bool>> ToExpression()
{
return movie => movie.ReleaseDate <= DateTime.Now.AddMonths(-MonthsBeforeDVDIsOut);
}
}
public sealed class MovieDirectedBySpecification : Specification<Movie>
{
private readonly string _director;
public MovieDirectedBySpecification(string director)
{
_director = director;
}
public override Expression<Func<Movie, bool>> ToExpression()
{
return movie => movie.Director.Name == _director;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment