Принцип единой ответственности против анемичной модели домена анти-паттерн


Я в проекте, который принимает принцип единой ответственности довольно серьезно. У нас много маленьких классов и все довольно просто. Однако у нас есть анемичная модель домена - нет никакого поведения ни в одном из наших классов моделей, они просто мешки свойств. Это не жалоба на наш дизайн - он на самом деле, кажется, работает довольно хорошо

во время обзоров дизайна SRP выводится всякий раз, когда в систему добавляется новое поведение, и поэтому новое поведение обычно заканчивается в новом классе. Это делает вещи очень легко проверяемыми на единицу, но я иногда недоумеваю, потому что это похоже на вытягивание поведения из того места, где оно уместно.

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

Если это так, то кажется, что конечным результатом является анемичная модель домена, что, безусловно, имеет место в нашем проекте. Тем не менее анемичная доменная модель является анти-паттерном.

могут ли эти две идеи сосуществовать?

редактировать: несколько контекстных ссылок:

SRP -http://www.objectmentor.com/resources/articles/srp.pdf
Анемичная Модель Предметной Области - http://martinfowler.com/bliki/AnemicDomainModel.html

Я не тот разработчик, который просто любит находить пророка и следовать тому, что они говорят как Евангелие. Поэтому я не предоставляю ссылки на них как способ заявить "это правила", так же как источник определения этих двух понятий.

7 57

7 ответов:

Я бы сказал "Да", но вы должны сделать свой SRP правильно. Если одна и та же операция применяется только к одному классу, она принадлежит к этому классу, не так ли? Как насчет того, что одна и та же операция применяется к нескольким классам? В этом случае, если вы хотите следовать модели OO объединения данных и поведения, вы бы поместили операцию в базовый класс, нет?

Я подозреваю, что из вашего описания, вы в конечном итоге с классами, которые в основном мешки операций, так что вы по существу воссоздан C-стиль кодирования: структуры и модули.

из связанной бумаги SRP: "SRP является одним из самых простых принципов, и один из самых трудных, чтобы получить право."

богатая модель домена (RDM) и принцип единой ответственности (SRP) не обязательно противоречат друг другу. РДМ больше расходится с узкоспециальной subclassof-свойство СРП - модели пропаганды "сведения бобы + вся бизнес-логика в классах контроллеров" (DBABLICC).

Если Вы читаете Мартина глава SRP, вы увидите его модем пример полностью находится на уровне домена, но абстрагирует понятия DataChannel и Connection как отдельные классы. Он держит модем сам по себе как обертка, так как это полезная абстракция для клиентского кода. Это гораздо больше о правильном (re)факторинг чем просто отводками. Сцепление и сцепление по-прежнему являются базовыми принципами проектирования.

наконец, три вопроса:

  • Как отмечает сам Мартин, не всегда легко увидеть различные "причины изменений". Сами понятия ЯГНИ, Agile и др. обескураживает ожидание будущих причин для перемен, поэтому мы не следует изобретать те, где они не сразу очевидны. Я вижу "преждевременные, ожидаемые причины для изменения" как реальный риск в применении SRP и должны управляться разработчиком.

  • дальше к предыдущему, даже правильно (но лишнее анал) применение SRP может привести к нежелательной сложности. Всегда думайте о следующем бедном парне, который должен поддерживать ваш класс: будет ли усердная абстракция тривиального поведения в свои собственные интерфейсы, базовые классы и однострочные реализации действительно помогают его пониманию того, что должно было быть просто одним классом?

  • разработка программного обеспечения часто заключается в получении наилучшего компромисса между конкурирующими силами. Например, многоуровневая архитектура в основном является хорошим приложением SRP, но как насчет того, что, например, изменение свойства бизнес-класса от, скажем, a boolean до перечисление есть эффект пульсации через все слои-от БД через домен, фасады, веб-сервис, до GUI? Указывает ли это на плохой дизайн? Не обязательно: это указывает на то, что ваш дизайн благоприятствует одному аспекту изменения к другому.

цитата из статьи SRP очень правильная; SRP трудно получить правильно. Этот и OCP-это два элемента SOLID, которые просто должны быть расслаблены, по крайней мере, в какой-то степени, чтобы фактически выполнить проект. Чрезмерное усердное применение любого из них будет очень быстро производить равиоли код.

SRP действительно можно принять до смешного, если "причины изменения" слишком конкретны. Даже Poco / POJO "Data bag" можно рассматривать как нарушение SRP, если вы рассматриваете тип поле меняется как "изменение". Вы думаете, что здравый смысл скажет вам, что изменение типа поля является необходимым условием для "изменения", но я видел доменные слои с оболочками для встроенных типов значений; АД, который делает ADM похожим на утопию.

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

У меня лично нет большой проблемы с" легким " доменом. Просто имея одну роль быть "экспертом по данным" делает объект домена хранитель каждого поля / свойства, относящегося к классу, а также вся логика вычисляемого поля, любые явные/неявные преобразования типов данных и, возможно, более простые правила проверки (т. е. обязательные поля, ограничения значений, вещи, которые нарушили бы экземпляр внутренне, если бы это было разрешено). Если алгоритм расчета, возможно, для взвешенного или скользящего среднего, вероятно, изменится, инкапсулируйте алгоритм и обратитесь к нему в вычисляемом поле (это просто хорошо OCP / PV).

Я не считаю такой объект домена "анемичным". Мое восприятие этого термина - это "пакет данных", набор полей, который не имеет никакого понятия о внешнем мире или даже о связи между его полями, кроме того, что он их содержит. Я тоже это видел, и это не весело отслеживать несоответствия в состоянии объекта, о которых объект никогда не знал, что это проблема. Чрезмерное усердие SRP приведет к этому, заявив, что объект данных не несет ответственности за какой-либо бизнес логика, но здравый смысл обычно вмешивается первым и говорит, что объект, как эксперт по данным, должен нести ответственность за поддержание согласованного внутреннего состояния.

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

мои $0.02.

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

Я бы не сказал, что это именно c-стиль программирования, где у вас есть структуры и модули, но скорее всего вы получите что-то более функциональное, я поймите, что стили похожи, но детали имеют большое значение. Я обнаружил, что мои экземпляры класса в конечном итоге ведут себя как функции более высокого порядка, приложение частичных функций, лениво оцененные функции или некоторая комбинация выше. Это несколько невыразимо для меня, но это чувство, которое я получаю от написания кода после TDD + SOLID, он в конечном итоге ведет себя как гибридный OO/функциональный стиль.

Что касается наследования быть плохим словом, я думаю, что это больше из-за того, что наследование недостаточно мелкозернисто в таких языках, как Java/C#. В других языках, это не проблема, и более полезным.

Мне нравится определение SRP как:

"У класса есть только одна бизнес-причина для изменения"

Итак, до тех пор, пока поведение может быть сгруппировано в единственные "бизнес-причины", тогда нет причин для того, чтобы они не сосуществовали в одном классе. Конечно, то, что определяет "деловую причину", открыто для обсуждения (и должно обсуждаться всеми заинтересованными сторонами).

прежде чем я начну мою речь, вот мое мнение в двух словах: где-то все должно сойтись... а потом через него протекает река.

меня преследует кодирование.

=======

анемичная модель данных и я... ну, мы часто дружим. Может быть, это просто природа малых и средних приложений с очень маленькой бизнес-логикой, встроенной в них. Может быть, я просто немного ошарашен.

однако, вот мой 2 центы:

не могли бы вы просто разложить код в сущностях и связать его с интерфейсом?

public class Object1
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    private IAction1 action1;

    public Object1(IAction1 action1)
    {
        this.action1 = action1;
    }

    public void DoAction1()
    {
        action1.Do(Property1);
    }
}

public interface IAction1
{
    void Do(string input1);
}

это как-то нарушает принципы СРП?

кроме того, не имеет ли куча классов, сидящих вокруг, не связанных друг с другом ничем, кроме потребляющего кода, на самом деле большим нарушением SRP, но подталкивает слой?

представьте себе, что парень пишет клиентский код, сидя там, пытаясь выяснить, как сделать что-то связанное к Объекту1. Если ему придется работать с вашей моделью, он будет работать с Object1, пакетом данных и кучей "услуг", каждый из которых несет одну ответственность. Это будет его работа, чтобы убедиться, что все эти вещи взаимодействуют должным образом. Таким образом, теперь его код становится скриптом транзакции, и этот скрипт сам будет содержать всю ответственность, необходимую для правильного завершения этой конкретной транзакции (или единицы работы).

кроме того, вы можете сказать: "нет бра, все, что ему нужно сделать, это получить доступ к службе слой. Это как Object1Service.DoActionX (Object1). Кусок пирога.- Ну и где же теперь логика? И все это в одном методе? Вы все еще просто толкаете код вокруг, и независимо от того, что вы получите, данные и логика будут разделены.

Итак, в этом сценарии, почему бы не предоставить клиентскому коду этот конкретный Object1Service и иметь его DoActionX() в основном просто еще один крючок для вашей модели домена? Под этим я подразумеваю:

public class Object1Service
{
    private Object1Repository repository;

    public  Object1Service(Object1Repository repository)
    {
        this.repository = repository;
    }

    // Tie in your Unit of Work Aspect'ing stuff or whatever if need be
    public void DoAction1(Object1DTO object1DTO)
    {
        Object1 object1 = repository.GetById(object1DTO.Id);
        object1.DoAction1();
        repository.Save(object1);
    }
}

вы все еще учитывали фактический код для Действие1 от Объект1, но и для всех интенсивных целях, не анемичный Объект1.

скажем, вам нужно Action1 для представления 2 (или более) различных операций, которые вы хотели бы сделать атомарными и разделенными на свои собственные классы. Просто создайте интерфейс для каждой атомной операции и подключите его внутри DoAction1.

вот как я мог бы подойти к этой ситуации. Но опять же, я действительно не знаю, что такое SRP.

преобразуйте ваши простые доменные объекты в ActiveRecord шаблон с общим базовым классом для всех объектов домена. Поместите общее поведение в базовый класс и переопределите поведение в производных классах, где это необходимо, или определите новое поведение, где это необходимо.