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

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

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

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

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

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

РЕДАКТИРОВАТЬ: пара контекстных ссылок:

SRP - https://web.archive.org/web/20150202200348/http://www.objectmentor.com/resources/articles/srp.pdf
Модель анемичного домена - http://martinfowler.com/bliki/AnemicDomainModel.html

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


person Niall Connaughton    schedule 09.09.2009    source источник
comment
Вы узнали много нового с тех пор, как разместили это? У меня за плечами несколько лет опыта разработки, и я нахожусь в подобной ситуации, которую вы описали, когда я вижу, что проект демонстрирует признаки модели анемичного домена, но я не уверен, как сбалансировать OOD с SRP.   -  person CamHart    schedule 02.02.2018
comment
Вау, это было давно. Я думаю, это отражает то, куда пошло ОО - далеко от того места, где оно началось. В колледже (много лет назад) я узнал, что объектно-ориентированный подход - это инкапсуляция данных со связанными действиями в классах и совместное использование поведения посредством наследования. Теперь это легкие классы данных с другими классами, которые могут воздействовать на них, склеенные вместе с помощью шаблонов проектирования и внедрения зависимостей, а наследование - это четырехбуквенное слово. SRP намного лучше работает со вторым стилем объектно-ориентированного программирования, чем с первым.   -  person Niall Connaughton    schedule 02.02.2018
comment
Что я узнал? Первоначальная идея моделирования сущностей как многофункциональных классов может привести к беспорядку иерархий и дыр, пробитых в инкапсуляции, чтобы позволить модели гибаться по-новому. Это плохо работает с TDD, что частично влияет на SRP и DI. В основном это нормально. Мы потеряли интуитивно понятные места в коде, где вы могли бы узнать, как ваша система выполняет определенные функции. Теперь вам нужно знать все задействованные классы акторов или сканировать базу кода. Мы полагаемся на IDE, чтобы найти способы использования, реализации интерфейсов и т. Д. Это отчасти способы справиться с тем, что нам принесли SRP и DI.   -  person Niall Connaughton    schedule 02.02.2018
comment
На высоком уровне, при переходе от моделей с богатой предметной областью к SRP, мы заменили чистые модели, которые развиваются в монстров, которых трудно разделить, на чистые наборы небольших классов, которые в конечном итоге превращаются в беспорядок, который никто не может полностью отслеживать. Разнообразные модели предметной области заманчивы, особенно на ранних этапах, но в конечном итоге вы обнаружите, что никогда не сможете идеально смоделировать проблему, и она может быстро испортиться. Подход SRP, вероятно, будет более гибким в долгосрочной перспективе, но ни один из них не принесет вам счастья на всю жизнь.   -  person Niall Connaughton    schedule 02.02.2018
comment
Спасибо, что вернулись так быстро. Похоже, серебряной пули нет. Я ценю компромиссы, на которые вы указываете.   -  person CamHart    schedule 02.02.2018
comment
Да, серебряной пули не бывает. Однако я бы добавил следующее: если вы собираетесь использовать SRP, усердно работайте, чтобы шаблоны проектирования были понятными, и сворачивайте избыточные слои / классы, когда они больше не нужны или слишком запутаны, чтобы понять, как они взаимодействуют. Если вы собираетесь использовать классы полнофункциональной модели предметной области, обязательно протестируйте их, чтобы заставить поведение, содержащееся внутри, управляться общедоступным интерфейсом. Если не удается сделать его общедоступным для тестирования, вероятно, сейчас самое подходящее время для создания нового класса.   -  person Niall Connaughton    schedule 02.02.2018


Ответы (7)


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

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

Из связанной статьи SRP: «SRP - один из самых простых принципов и один из самых сложных в реализации.»

person CPerkins    schedule 09.09.2009
comment
Хм ... Хотелось бы убедиться, но это не так. Предпочтение композиции перед наследованием противоречит вашим аргументам ... - person Mark Seemann; 09.09.2009
comment
Хорошо, мы можем обмениваться цитатами из Священных Писаний или спорить о том, требует ли милость абсолютизма. Или мы можем попробовать решить проблему OP. Что вы думаете о том, как решить проблему с OP? - person CPerkins; 09.09.2009
comment
Спасибо за Ваш ответ. В наши дни я обнаружил, что объектно-ориентированная модель объединения данных и поведения очень вышла из моды. Я, конечно, знаю о ловушках, которые могут возникнуть при сложных иерархиях наследования, но мне кажется, что в наши дни слово «наследование» стало почти запретным словом. SRP и предпочтение композиции по наследованию, похоже, отталкивают модели с богатой предметной областью. Я думаю, что проблемы возникают, когда принципы соблюдаются потому, что они принципы, а не потому, что они лучше всего подходят для вашего проекта. Как вы говорите, цитаты из Священных Писаний. - person Niall Connaughton; 10.09.2009
comment
Ваше предпоследнее предложение очень мудрое, но оно приведет к разногласиям со многими людьми. Идеология проще анализа. - person CPerkins; 10.09.2009
comment
@CPerkins: Я следил за этим вопросом, потому что искренне заинтересован в получении ответа - у меня почти такой же опыт, как композиция вместо наследования, как правило, делает модели предметной области более анемичными. Просто у композиции есть много других преимуществ, поэтому я не хочу моделировать логику наследованием, если это не имеет смысла. Если бы у меня был ответ получше, я бы опубликовал его. Мне бы очень хотелось получить убедительный ответ, но я полагаю, что мне нужно будет продолжить заниматься этим профессионально. Это не имеет ничего общего с «писанием». - person Mark Seemann; 14.09.2009
comment
@Mark - достаточно честно. Одна из проблем, с которыми мы сталкиваемся, заключается в том, что передовые практики несовместимы. Это как если бы мы все учились водить машину, а также учились и преподавали такие практики, как: не езжай слишком быстро и не езжай слишком медленно ... с общим отсутствием знаний о том, что является правильной скоростью в данной ситуации. . Приношу свои извинения за предположение, что ваше простое упоминание подсказки Блоха было идеологическим - правда, я сталкиваюсь с этим чаще, чем я бы надеялся, но это не оправдание для того, чтобы думать об этом о вас. - person CPerkins; 14.09.2009

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

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

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

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

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

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

person Cornel Masson    schedule 03.06.2010
comment
На самом деле DBABLICC - это не форма SRP, а скорее способ для процедурных программистов рационализировать свое процедурное программирование с использованием языка ООП. - person Sled; 14.10.2011

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

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

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

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

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

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

My $0.02.

person KeithS    schedule 31.08.2010
comment
Я думаю, что ваш комментарий в конце о том, что объект должен отвечать за поддержание согласованного внутреннего состояния, является настоящим решающим аргументом. Анемичные модели, которые я видел, оставляют это классам, которые манипулируют моделью через общедоступные сеттеры, основываясь на идее, что вычисление значения - это другая ответственность по сравнению с предоставлением значения, независимо от сложности вычисления. - person Niall Connaughton; 01.09.2010

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

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

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

person Saem    schedule 14.09.2009

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

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

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

person JontyMC    schedule 29.10.2009

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

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

=======

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

Однако вот мои 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, пакетом данных и кучей «сервисов», каждая из которых несет единственную ответственность. Его работа - убедиться, что все эти вещи взаимодействуют должным образом. Таким образом, теперь его код становится сценарием транзакции, и этот сценарий сам будет содержать всю ответственность, необходимую для правильного завершения этой конкретной транзакции (или единицы работы).

Более того, вы могли бы сказать: «Нет, братан, все, что ему нужно сделать, это получить доступ к уровню обслуживания. Это как 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.

person Merritt    schedule 14.03.2012

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

person this. __curious_geek    schedule 03.06.2010