Анемичная и «Богатая» модель в контексте GRASP шаблонов

    В недавнем выпуске подкаста DotNet&More мы обсуждали с Максимом Аршиновым его предстоящий доклад на Московский .Next "Блеск и нищета предметной модели". С позицией Максима можно будет легко познакомиться непосредственно на конференции. И, в качестве дополнения, я бы хотел рассмотреть видение великого спора Анемичная VS "Богатая" ("Насыщенная") доменные модели через призму некогда популярных GRASP шаблонов


    Дискуссии идут достаточно давно, например:



    Прежде чем начать разбор, хотелось бы прояснить предмет спора. Основное отличие анемичной модели от богатой в том, что она не содержит бизнес логику в теле класса. Частным примером анемичной модели может быть всем известный паттерн DTO.


    "Богатая" модель, в свою очередь, может содержать логику, описывающую бизнес правила, функции и т.д. Обратите внимание, что мы рассматриваем именно методы, отражающие бизнес логику. ToString, GetHashCode и прочие "технические" части классов не входят в предмет обсуждения, потому и игнорируются.


    GRASP


    Как и отмечалось в начале статьи, рассматривать данный вопрос мы будем в контексте GRASP паттернов. Данный набор шаблонов был представлен в книге Крэга Лармана «Применение UML и шаблонов проектирования» и сильно повлиял на современное программирование, например, правила Low Coupling/High Cohesion были объявлены именно в GRASP.


    Для людей, знакомых с GoF паттернами данный набор шаблонов может показаться слишком размытым. Все дело в том, что GRASP паттерны сфокусированы не на решении прикладных задач, а на распределении ответственности за те или иные действия и операции между объектами:


    • Creator отвечает за создание объектов
    • Controller отвечает за операции от пользователей
    • Indirection отвечает за организацию слабого зацепления между объектами
      И так далее.

    В контексте данной статьи хотелось бы сфокусироваться на следующих паттернах:


    • Information Expert (Информационный эксперт)
    • Pure Fabrication (Чистая выдумка)

    Information Expert (Информационный эксперт)


    Проблема: Каков базовый принцип распределения обязанностей между объектами?
    Решение: Назначить эту обязанность тому классу, который обладает достаточной информацией для ее выполнения.


    Другими словами:


    Ответственность должна быть назначена тому, кто владеет максимумом необходимой информации для исполнения — информационному эксперту.

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


    Pure Fabrication (Чистая выдумка)


    Проблема: Какой класс должен обеспечить реализацию High Cohesion и Low Coupling, если шаблон Information Expert не обеспечивает подходящего решения.
    Решение: Присвоить группу обязанностей с высокой степенью зацепления искусственному классу, не представляющему конкретного понятия предметной области.


    Другими словами:


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

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


    И что это все значит?


    В контексте вышесказанного, можно выделить достаточно простой алгоритм, помогающий принять решение, располагать метод в модели или в сервисе:


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


    Резюме


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

    • +11
    • 1,2k
    • 5
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 5

      0

      Why it is shown in the English language feed?

        0
        Sorry, misclick.
        +1
        Автору спасибо за статью, — интересная точка зрения.
        то данный метод стоит объявить в отдельном классе. И эти классы называют сервисами.

        Стоит добавить, что Сервисы бывают трех уровней: Application, Domain и Infrastructure. В данном примере Вы говорите именно о Доменном Сервисе. Ограниченный список оснований для создания Доменных Сервисов хорошо выразил Vaughn Vernon в «Implementing Domain-Driven Design».
          0
          Спасибо, статья Вернона замечательна
            0
            Я имел ввиду книгу Вернона, она сегодня, пожалуй, вторая, если даже не первая, по значимости в сообществе DDD. А в статью цитату Вернона вставил уже я. Но, рад, если понравилось.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое