Подтвердить что ты не робот

Единый принцип ответственности против антиобщественной модели доменных доменов

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

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

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

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

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

EDIT: несколько ссылок, связанных с контекстом:

SRP - http://www.objectmentor.com/resources/articles/srp.pdf
Анемическая модель домена - http://martinfowler.com/bliki/AnemicDomainModel.html

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

4b9b3361

Ответ 1

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

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

Из связанного документа SRP: "СРП является одним из самых простых из принципа, и одним из самых трудных для правильного".

Ответ 2

Богатая модель домена (RDM) и принцип одиночной ответственности (SRP) не обязательно имеют разногласия. RDM больше расходится с очень специализированным подклассом SRP - модели, защищающей "данные beans + всю бизнес-логику в классах контроллеров" (DBABLICC).

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

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

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

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

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

Ответ 3

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

SRP действительно может быть взят до смешных длин, если "причины изменения" слишком специфичны. Даже пакет данных POCO/POJO можно рассматривать как нарушение SRP, если вы рассматриваете тип поля, изменяющийся как "изменение". Вы считаете, что здравый смысл подскажет вам, что изменение типа поля является необходимым для "изменения", но я видел слои домена с обертками для встроенных типов значений; ад, который делает ADM похожим на Utopia.

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

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

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

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

Мои $0,02.

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

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

=======

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

Однако здесь мои 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?

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

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

Кроме того, вы можете сказать: "нет брах, все, что ему нужно сделать, - это получить доступ к служебному слою, как 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);
    }
}

Вы все еще учли фактический код для Action1 из Object1, но для всех интенсивных целей имеете неанемический Object1.

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

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

Ответ 7

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