Как реализовать шаблон спецификации?

В моем проекте; Я включил определенные классы шаблонов, которые приведены ниже. Я не знаю, как это реализовать. Эти коды включены предыдущими разработчиками.

public interface ISpecification<T>
{
    Expression<Func<T, bool>> SpecExpression { get; }
    bool IsSatisfiedBy(T obj);
}

public static class IExtensions
{
    public static ISpecification<T> And<T>(
        this ISpecification<T> left,
        ISpecification<T> right)
    {
        return new And<T>(left, right);
    }

    public static ISpecification<T> Or<T>(
        this ISpecification<T> left,
        ISpecification<T> right)
    {
        return new Or<T>(left, right);
    }

    public static ISpecification<T> Negate<T>(this ISpecification<T> inner)
    {
        return new Negated<T>(inner);
    }
}

public abstract class SpecificationBase<T> : ISpecification<T>
{
    private Func<T, bool> _compiledExpression;

    private Func<T, bool> CompiledExpression
    {
        get { return _compiledExpression ?? (_compiledExpression = SpecExpression.Compile()); }
    }

    public abstract Expression<Func<T, bool>> SpecExpression { get; }

    public bool IsSatisfiedBy(T obj)
    {
        return CompiledExpression(obj);
    }
}

public class And<T> : SpecificationBase<T>
{
    ISpecification<T> left;
    ISpecification<T> right;

    public And(
        ISpecification<T> left,
        ISpecification<T> right)
    {
        this.left = left;
        this.right = right;
    }

    // AndSpecification
    public override Expression<Func<T, bool>> SpecExpression
    {
        get
        {
            var objParam = Expression.Parameter(typeof(T), "obj");

            var newExpr = Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(
                    Expression.Invoke(left.SpecExpression, objParam),
                    Expression.Invoke(right.SpecExpression, objParam)
                ),
                objParam
            );

            return newExpr;
        }
    }
}

public class Or<T> : SpecificationBase<T>
{
    ISpecification<T> left;
    ISpecification<T> right;

    public Or(
        ISpecification<T> left,
        ISpecification<T> right)
    {
        this.left = left;
        this.right = right;
    }

    // OrSpecification
    public override Expression<Func<T, bool>> SpecExpression
    {
        get
        {
            var objParam = Expression.Parameter(typeof(T), "obj");

            var newExpr = Expression.Lambda<Func<T, bool>>(
                Expression.OrElse(
                    Expression.Invoke(left.SpecExpression, objParam),
                    Expression.Invoke(right.SpecExpression, objParam)
                ),
                objParam
            );

            return newExpr;
        }
    }
}

 public class Negated<T> : SpecificationBase<T>
{
    private readonly ISpecification<T> _inner;

    public Negated(ISpecification<T> inner)
    {
        _inner = inner;
    }

    // NegatedSpecification
    public override Expression<Func<T, bool>> SpecExpression
    {
        get
        {
            var objParam = Expression.Parameter(typeof(T), "obj");

            var newExpr = Expression.Lambda<Func<T, bool>>(
                Expression.Not(
                    Expression.Invoke(this._inner.SpecExpression, objParam)
                ),
                objParam
            );

            return newExpr;
        }
    }
}

Как реализовать приведенную выше спецификацию на простом примере? Какая польза от этой спецификации?


person Pradees    schedule 01.03.2017    source источник
comment
Это шаблон спецификации, реализованный с помощью Expression. Вариант использования зависит от модели вашего домена.   -  person Ofir Winegarten    schedule 01.03.2017
comment
@Ofir Winegarten Можете ли вы привести небольшой пример с вышеуказанными классами? Как это использовать?   -  person Pradees    schedule 01.03.2017
comment
Хотя шаблон спецификации очень полезен для сохранения знаний предметной области на уровне предметной области, вы всегда должны учитывать, не должно ли правило быть частью самой сущности. Например. зачем иметь CustomerIsEligibleForPromotionSpecification, если вы можете иметь метод в самой сущности Customer: bool IsEligibleForPromotion. Имеет смысл иметь специализированный класс Specification, когда для проверки нужны данные, которых нет в объекте/агрегате. В противном случае это, вероятно, должен быть просто метод.   -  person Phillippe Santana    schedule 02.08.2019


Ответы (1)


Как я писал в комментариях, это шаблон спецификации, реализованный с помощью Expression.

Допустим, у нас есть следующая Domain-Model:

public class Person
{
    public string Name { get; set; }
    public DateTime BirthDate { get; set; }
    public string Country { get; set; }
}

И, кроме того, у нас есть список из них:

List<Person> persons; // <-- initialized elsewhere

Теперь у нас может быть две спецификации для них. Давайте сделаем один для тех, кто живет в Spain, и один для тех, кто родился до 01/01/2000.

public class SpainSpec : SpecificationBase<Person>
{
    public override Expression<Func<Person, bool>> SpecExpression => person => person.Country == "Spain";
}

public class BornBefore2000 : SpecificationBase<Person>
{
    public override Expression<Func<Person, bool>> SpecExpression => person => person.BirthDate < DateTime.Parse("2000-01-01");
}

Теперь мы можем использовать его, чтобы найти всех людей, родившихся до 2000 года:

ISpecification spec = new SpainSpec();
persons.Where (spec.IsSatisfiedBy);

Вы, конечно, можете связать их, чтобы получить тех из Испании, которые родились до 2000 года:

ISpecification spec = new SpainSpec().And(new BornBefore2000());
persons.Where (spec.IsSatisfiedBy);

Это действительно очень простой сценарий, у вас может быть гораздо больше, в зависимости от вашей модели и потребностей.

Будьте осторожны при использовании спецификаций, чтобы не потерять контроль над ними и не иметь слишком много классов или не изобретать велосипед.

person Ofir Winegarten    schedule 01.03.2017