Вопрос

Мне просто интересно, является ли шаблон спецификации бессмысленной, приведенный следующим примером:

Скажем, вы хотите проверить, достаточно ли клиента в его/ее учетной записи, вы создали бы спецификацию что -то вроде:

new CustomerHasEnoughMoneyInTheirAccount().IsSatisfiedBy(customer)

Тем не менее, мне интересно, я могу достичь тех же «преимуществ» схемы спецификации (например, необходимость изменить правила бизнеса на месте), используя Getter имущество в классе клиентов, как это:

public class Customer
{

     public double Balance;

     public bool HasEnoughMoney
     {
          get { return this.Balance > 0; }
     }
}

Из клиентского кода:

customer.HasEnoughMoney

Так что мой вопрос действительно; В чем разница между использованием собственности Getter для завершения бизнес -логики и созданием класса спецификации?

Спасибо всем заранее!

Это было полезно?

Решение

Потому что с классом спецификации вы можете создавать новые критерии без модификации самих объектов.

Другие советы

В общем смысле объект спецификации - это просто предикат, завернутый в объект. Если предикат очень часто используется с классом, это может иметь смысл Метод перемещения Предикат в класс, к которому он относится.

Эта модель действительно входит в себя, когда вы создаете что -то более сложное, как это:

var spec = new All(new CustomerHasFunds(500.00m),
                   new CustomerAccountAgeAtLeast(TimeSpan.FromDays(180)),
                   new CustomerLocatedInState("NY"));

и передавать его или сериализовать; Это может иметь еще больший смысл, когда вы предоставляете какой -то пользовательский интерфейс «спецификации».

Тем не менее, C# предоставляет более идиоматические способы выражения подобных вещей, такие как методы расширения и LINQ:

var cutoffDate = DateTime.UtcNow - TimeSpan.FromDays(180); // captured
Expression<Func<Customer, bool>> filter =
    cust => (cust.AvailableFunds >= 500.00m &&
             cust.AccountOpenDateTime >= cutoffDate &&
             cust.Address.State == "NY");

Я играл с некоторым экспериментальным кодом, который реализует спецификации с точки зрения ExpressionS, с очень простыми методами статического строителя.

public partial class Customer
{
    public static partial class Specification
    {
        public static Expression<Func<Customer, bool>> HasFunds(decimal amount)
        {
            return c => c.AvailableFunds >= amount;
        }

        public static Expression<Func<Customer, bool>> AccountAgedAtLeast(TimeSpan age)
        {
            return c => c.AccountOpenDateTime <= DateTime.UtcNow - age;
        }


        public static Expression<Func<Customer, bool>> LocatedInState(string state)
        {
            return c => c.Address.State == state;
        }
    }
}

Что сказано, Это целая нагрузка шаблона, которая не добавляет значения! Эти ExpressionS смотрите только на общественные свойства, поэтому можно так же легко использовать простую старую лямбду! Теперь, если одна из этих технических характеристик необходимо получить доступ к непубличному состоянию, мы действительно делать Нужен метод застройщика с доступом к непубличным состояниям. Я буду использовать lastCreditScore В качестве примера здесь.

public partial class Customer
{
    private int lastCreditScore;

    public static partial class Specification
    { 
        public static Expression<Func<Customer, bool>> LastCreditScoreAtLeast(int score)
        {
            return c => c.lastCreditScore >= score;
        }
    }
}

Нам также нужен способ составить композицию этих спецификаций - в данном случае, композит, который требует от того, чтобы все дети были правдой:

public static partial class Specification
{
    public static Expression<Func<T, bool>> All<T>(params Expression<Func<T, bool>>[] tail)
    {
        if (tail == null || tail.Length == 0) return _0 => true;
        var param = Expression.Parameter(typeof(T), "_0");
        var body = tail.Reverse()
            .Skip(1)
            .Aggregate((Expression)Expression.Invoke(tail.Last(), param),
                       (current, item) =>
                           Expression.AndAlso(Expression.Invoke(item, param),
                                              current));

        return Expression.Lambda<Func<T, bool>>(body, param);
    }
}

Я предполагаю, что часть недостатка в этом - это может привести к сложному Expression деревья. Например, построение этого:

 var spec = Specification.All(Customer.Specification.HasFunds(500.00m),
                              Customer.Specification.AccountAgedAtLeast(TimeSpan.FromDays(180)),
                              Customer.Specification.LocatedInState("NY"),
                              Customer.Specification.LastCreditScoreAtLeast(667));

производит Expression Дерево, которое выглядит так. (Это слегка отформатированные версии того, что ToString() возвращает, когда вызовут на Expression - Обратите внимание, что вы не сможете увидеть структуру выражения вообще, если у вас был только простой делегат! Пара заметок: DisplayClass это класс, сгенерированный компилятором, который содержит локальные переменные, захваченные в закрытии, чтобы справиться с Вверх Фунарг Проблема; и сброшен Expression использует один = знак для представления сравнения равенства, а не типичного C# ==.)

_0 => (Invoke(c => (c.AvailableFunds >= value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass0).amount),_0)
       && (Invoke(c => (c.AccountOpenDateTime <= (DateTime.UtcNow - value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass2).age)),_0) 
           && (Invoke(c => (c.Address.State = value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass4).state),_0)
               && Invoke(c => (c.lastCreditScore >= value(ExpressionExperiment.Customer+Specification+<>c__DisplayClass6).score),_0))))

Беспорядочный! Много вызова немедленных лямбдов и сохранило ссылки на закрытие, созданные в методах строителя. Заменив ссылки на закрытие на их захваченные значения и β-восстановление вложенные лямбдас (я тоже α-конвертированный Все имена параметров на уникальные сгенерированные символы как промежуточный шаг для упрощения β-восстановления), гораздо проще Expression Результаты дерева:

_0 => ((_0.AvailableFunds >= 500.00)
       && ((_0.AccountOpenDateTime <= (DateTime.UtcNow - 180.00:00:00))
           && ((_0.Address.State = "NY")
               && (_0.lastCreditScore >= 667))))

Эти Expression Деревья могут затем быть дополнительно объединены, скомпилированы в делегатов, довольно отредактированные, отредактированные, переданные в интерфейсы LINQ, которые понимают Expression Деревья (такие как те, которые предоставлены EF), или что у вас есть.

С другой стороны, я построил глупый маленький микроэлемент и фактически обнаружил, что устранение ссылок на закрытие оказало замечательное влияние на скорость оценки примера Expression При составлении делегату - он сократил время оценки почти вдвое (!), С 134,1NS до 70,5 нс за вызов на машине, от которой я сидел перед. С другой стороны, β-восстановление не имела обнаруживаемой разницы, возможно, потому что компиляция делает это в любом случае. В любом случае, я сомневаюсь, что обычный набор класса спецификации может достичь такой скорости оценки для совокупности из четырех условий; Если такой обычный класс должен был быть построен по другим причинам, таким как удобство кода Builder-UI, я думаю, что было бы целесообразно, чтобы набор класса производил Expression Вместо того, чтобы напрямую оценивать, но сначала подумайте, нужен ли вам шаблон вообще в C# - я видел слишком много кода, обогнанного спецификации.

Да, это бессмысленно.

Статья Википедии критикует эту модель подробно. Но я вижу самую большую критику - исключительно Внутренний эффект. Анкет Зачем повторно изобретать и оператор? Опять же, прочитайте статью в Википедии за более полную критику.

Генри, вы правы, чтобы предположить, что недвижимость получила превосходство. Зачем избегать более простой, хорошо понятой концепции OO, для неясного «шаблона», которая в своей концепции не отвечает на ваш вопрос? Это идея, но плохая. Это анти-паттерн, шаблон, который работает против вас, кодер.

Вы спросили, в чем разница, но более полезный вопрос: когда следует использовать шаблон спецификации?

Никогда не используйте этот шаблон, мое общее правило для этой модели.

Во -первых, вы должны понимать, что это не общая теория, это только конкретный шаблон; с конкретным моделированием классов {спецификация и спецификация, ...}. Имея в виду более широкую теорию, ориентированную на домен, вы можете отказаться от этой модели и при этом иметь превосходные варианты, с которыми все знакомы: например, хорошо названные объекты/методы/свойства для моделирования домена и логики.

Джеффри сказал:

Спецификационный объект - это просто предикат, завернутый в объект

Это верно для домена, но не к шаблону спецификации конкретно. Джеффри, всесторонне описывает ситуацию, в которой можно динамически создать выражение, так и эффективно выполнять в хранилище данных (база данных SQL). Его окончательный вывод заключается в том, что вы не можете сделать это с шаблоном спецификации, как это предписано. Икиируемые выражения деревьев Джеффри являются одним из альтернативных способов выделения логических правил и применения их в разных композитах. Как вы можете видеть из его примера кода, с ним словесно и очень неловко работать. Я не могу представить какую -либо ситуацию, которая также потребует таких динамических композитов. И при необходимости есть много других доступных методов, которые проще.

Мы все знаем, что вы должны оптимизировать производительность в последний раз. Попытка здесь достичь Кровоточащий край С iQueryable Expression Trees - это ловушка. Вместо этого начните с лучших инструментов, сначала простой и территории. Затем протестируйте, оцените и расставьте приоритеты, какую работу остается.

Я еще не испытал ситуацию, когда эта схема спецификации необходима/лучше. Когда я сталкиваюсь с предполагаемыми ситуациями, я перечислю их здесь и опроверму их. Если я наткнусь на хорошую ситуацию, я пересмотрю этот ответ новым разделом.

Re: Zerkms ответ

Потому что с классом спецификации вы можете создавать новые критерии [sic] без модификации самих объектов.

C# уже обслуживает такие ситуации:

  • Наследование (в целом), где вы затем расширяете унаследованный класс (это хорошо, если у вас нет пространства имен/библиотека откуда, откуда приходит класс)
  • Метод переопределяется в наследстве
  • Частично - Отлично, когда у вас есть классы модели данных. Вы можете добавить [указанные] свойства вместе и насладиться всем блаженством доступа к необходимой информации, которая вам нужна непосредственно от объекта. Когда ты нажимаешь '.' Intellisense говорит вам, какие участники доступны.
  • Методы расширения великолепны, когда наследование не является практичным (архитектура не поддерживает ее), или если родительский класс запечатан.

В проектах, из которых я принимаю во владение, я сталкиваюсь с анти-паттернами, такими как шаблон спецификации, и многое другое. Они часто находятся в отдельном проекте/библиотеке (чрезмерная фрагментация проектов является еще одной ужасной практикой), и все слишком напуганы, чтобы расширить объекты.

См. Ответ Zerkms, плюс: спецификация также может работать на абстрактных типах, таких как интерфейсы, или в качестве общего, что делает его применимым для целого диапазона объектов.

Или проверка, которая должна быть сделана на клиенте, может зависеть от контекста. Например, объект клиента может быть недопустимым для системы ролевых ролей, но действителен для его сохранения в базе данных в середине процесса для дальнейшей обработки, когда пользователь снова входит в систему. С техническими характеристиками вы можете создавать группы связанных проверок в централизованном месте и переключить весь набор в зависимости от контекста. В этой ситуации вы, например, объедините его с заводской шаблоном.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top