Следует ли сопоставить объект домена с моделью представления с помощью необязательного конструктора?

Я хотел бы иметь возможность сопоставить модель предметной области с моделью представления путем создания новой модели представления и передачи модели предметной области в качестве параметра (как в приведенном ниже коде). Моя мотивация состоит в том, чтобы воздержаться от повторного использования кода сопоставления И предоставить простой способ сопоставления (еще не используя автомаппер). Друг говорит, что модель представления не должна ничего знать о модели предметной области «оплаты», которая передается в необязательный конструктор. Что вы думаете?

public class LineItemsViewModel
{
    public LineItemsViewModel()
    {
    }

    public LineItemsViewModel(IPayment payment)
    {
        LineItemColumnHeaders = payment.MerchantContext.Profile.UiPreferences.LineItemColumnHeaders;
        LineItems = LineItemDomainToViewModelMapper.MapToViewModel(payment.LineItems);
        ConvenienceFeeAmount = payment.ConvenienceFee.Fee;
        SubTotal = payment.PaymentAmount;
        Total = payment.PaymentAmount + payment.ConvenienceFee.Fee;
    }

    public IEnumerable<Dictionary<int, string>> LineItems { get; set; }
    public Dictionary<int, string> LineItemColumnHeaders { get; set; }
    public decimal SubTotal { get; set; }
    public decimal ConvenienceFeeAmount { get; set; }
    public decimal Total { get; set; }
}

person Byron Sommardahl    schedule 18.05.2010    source источник
comment
Если Total является концепцией предметной области (а не просто полем, показанным в представлении), вы должны использовать логику Total = payment.PaymentAmount + payment.ConvenienceFee.Fee внутри свойства или метода в вашей модели предметной области. Бизнес-логика не принадлежит модели представления.   -  person Ryan    schedule 18.05.2010


Ответы (2)


Ваш друг прав. Представления должны быть тупыми и ничего не знать о вашей модели предметной области.

Пробовали ли вы использовать Automapper для сопоставления сущностей / моделей вашего бизнеса / домена с вашими dto / viewmodels?

Подробнее из-за комментария:

Помещение кода сопоставления в ваши модели представления нарушает разделение ответственности, принципала единственной ответственности SOLID, шаблон MVC и принципалов проектирования, ориентированных на предметную область. У представлений одна обязанность - выводить данные на экран, вот и все. ИМХО тут не о чем спорить. Это просто плохая идея, нарушающая многие основные принципы разработки программного обеспечения.

person John Farrell    schedule 18.05.2010
comment
Я ценю ответ. Я хочу поспорить, чтобы получить наиболее полный ответ. Я говорю о модели представления и о конструкторе класса модели представления. Можете ли вы указать причину (или более), почему объект модели представления не должен выполнять собственное сопоставление (через метод или конструктор). - person Byron Sommardahl; 19.05.2010
comment
@Byron Я позволяю своим DTO выполнять внутреннее отображение как неявное приведение из объекта домена. Иногда я использую DTO в качестве модели представления, а иногда объединяю несколько DTO в составную виртуальную машину. (Я знаю, что это, вероятно, не будет популярным!) Раньше я использовал Automapper, но мне довелось столкнуться с достаточно сложными сценариями, которые так же быстро можно написать вручную; плюс он сохраняет логику проекции с DTO / VM. Что вы выберете, зависит от вашего сценария. - person Ryan; 19.05.2010
comment
Хотя код сопоставления действительно должен где-то существовать, размещение его внутри конструктора модели представления связывает его с внутренней структурой домена. Если по какой-то причине вам нужно заполнить модель представления из другого источника, вам придется добавить еще один конструктор для этого с помощью этого метода. Если отвлечься, jfar прав в том, что он просто сводится к разделению проблем. Хотя размещение кода сопоставления может показаться удобным, почему модель представления должна заботиться о том, откуда берутся данные? - person Derek Greer; 24.05.2010
comment
Я согласен с ответом jfar, но тогда где мы должны отображать домен для просмотра моделей? Ни в модели представления, ни в репозитории для разделения задач, ни в контроллере для проверки. Должны ли мы добавить еще один слой для отображения домена для просмотра моделей? - person Michael; 02.07.2014
comment
@DerekGreer - ваш комментарий имеет смысл для меня, однако я пытаюсь убедить друга, который все время повторяет: дайте мне одно практическое плохое следствие наличия нескольких конструкторов в модели представления / нарушения разделения ответственности здесь. Я сказал ему, что это нарушает SRP, но он все еще говорит - а что, если это нарушает теоретический принцип? дайте мне одну плохую ситуацию, которая может произойти, что оправдает добавление еще одного слоя сопоставления вместо того, чтобы делать это в конструкторах в режиме просмотра? Не могли бы вы привести мне конкретный пример, который мог бы его убедить? - person BornToCode; 14.12.2015
comment
@BornToCode Я оставлю новый ответ на этот вопрос, так как подозреваю, что раздел комментариев будет слишком ограниченным, чтобы вместить ответ на ваш вопрос. - person Derek Greer; 19.12.2015

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

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

Конкретная практика создания конструктора для сопоставления значений одного объекта с другим создает связь между двумя объектами. Модель представления содержит свойства и / или поведение, относящиеся к конкретному представлению в системе. Поскольку целью такого объекта является моделирование определенного представления, инкапсуляция логики сопоставления для инициализации внутреннего состояния / значений модели из другого типа в системе означает, что модель представления теперь содержит код, который может потребоваться изменить по причинам. кроме изменений в способе моделирования представления. Такие изменения открывают возможность неблагоприятного воздействия на другие аспекты поведения модели, вызывая тем самым непреднамеренный регресс в поведении системы. Принцип, регулирующий разделение компонентов внутри системы для предотвращения непреднамеренного регресса поведения за счет объединения нескольких проблем, называется принципом единой ответственности.

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

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

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

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

// constructor
public ViewType(DomainType domainType) {
...
}


// mapper class
public class ViewTypeMapper { 
  public ViewType Map(DomainType domainType) {
  ...
  }
}

Итак, вы либо выполняете return new ViewType (domainType), либо выполняете return new ViewTypeMapper (). Map (domainType). Я просто не вижу, где развязка в этом случае добавляет существенной работы. В большинстве случаев вы уже зря потратили время и деньги своей компании или клиента, даже обсудив это, потому что в конечном итоге вы неизбежно будете говорить об этом в течение более длительного периода времени, чем если бы вы просто создавали отдельные классы для представления сопоставления, или если вы просто настроите Automapper.

person Derek Greer    schedule 21.12.2015
comment
Ваш ответ - шедевр. Не могли бы вы привести пример изменения, которое отрицательно повлияло бы на модель представления, если бы код сопоставления находился внутри конструктора, но было бы безопасно, если бы сопоставление было выполнено в выделенном слое сопоставления? - person BornToCode; 25.12.2015
comment
Учтите, что два варианта использования с разными API-интерфейсами прикладного уровня привели к возврату одной и той же модели представления (например, порядок просмотра, порядок отмены) и что была некоторая сложность в логике сопоставления (например, некоторая фильтрация LINQ, преобразование даты и т. Д.) . Возможно, первый вариант использования работает правильно, но необходимость изменения кода сопоставления для некоторых уникальных потребностей второго варианта может привести к поломке первого. Это тот тип регрессии, которого SRP помогает избежать. - person Derek Greer; 28.12.2015
comment
Кроме того, такой код отображения добавляет ненужную сложность. Когда разработчик изучает конкретный модуль кода, весь код, с которым он сталкивается, должен соответствовать тому случаю, который он пытается понять в идеале. Когда у класса есть несколько обязанностей, понимание того, как работает код, требует от разработчика понимания кода, который на самом деле не имеет отношения к варианту использования, над которым он работает (т.е. нарушения SRP добавляют когнитивную нагрузку). Конечно, такие проблемы с меньшей вероятностью возникнут или окажут существенное влияние на проблемы, связанные с картированием. Я привожу их только в качестве общих примеров. - person Derek Greer; 28.12.2015
comment
Спасибо за ваши ответы. Что касается вашего первого комментария, я все еще не понял его - если сопоставление будет выполнено в классе orderVMMapper (забудьте на данный момент о automapper) - я бы невинно изменил этот класс для потребностей `` отменить порядок '', но это все равно нарушит порядок просмотра ', потому что изначально я бы написал контроллер порядка просмотра для использования того же orderVMMapper (чтобы соответствовать DRY). Что мне не хватает? - person BornToCode; 29.12.2015
comment
Сценарий, который я описал, включает отображение результатов двух разных ответов на уровне приложения (т. Е. Разных API уровня приложения) на одну и ту же модель представления. - person Derek Greer; 29.12.2015
comment
Итак, если я правильно понимаю, вы имели в виду сценарий типа class CanceledOrderDomainModel : BaseOrder, class ActiveOrdersDomainModel : BaseOrder 1. Отображение в конструкторе: class MyViewModel { public MyViewModel(BaseOrder order) { // mapping here } } vs 2. Отображение на выделенном слое: class BaseOrderMapper { public MyViewModel Map(BaseOrder order) {// mapping here} } Но все же я, вероятно, сопоставлю оба результата API внутри одного и того же метода карты в моем классе сопоставления ( в соответствии с DRY), поэтому модификация одного может нарушить работу другого. Так как же выделенный слой может сделать мои изменения более безопасными? - person BornToCode; 29.12.2015
comment
Это не будет хорошим примером, учитывая, что вы говорите, что различия между двумя типами моделей предметной области несущественны. Я имею в виду что-то вроде CancelOrderResponse и PlaceOrderResponse, которые содержат разные представления одних и тех же данных, которые необходимо сопоставить с одним и тем же OrdersViewModel. Учитывая, что логика сопоставления нетривиальна, изменения для одного варианта использования могут непреднамеренно повлиять на другой вариант использования. - person Derek Greer; 29.12.2015
comment
Спасибо за разъяснение. Если у меня есть свойство ActiveOrders в моей модели представления. И 2 конструктора отображения. Один делает: ActiveOrders = cancelResponse.Orders.Where(o=>o.IsActive) другой делает: ActiveOrders = placeOrderResponse.Orders.Where(o=>o.IsActive && o.Approved). Как изменения в одном конструкторе могут повлиять на другой? (Я понимаю, что у меня может возникнуть соблазн изменить общее свойство ActiveOrders для конкретного варианта использования, потому что я уже внутри этого класса, но, похоже, вы имеете в виду изменение внутри самого конструктора сопоставления? Не могли бы вы привести пример такого изменять? - person BornToCode; 30.12.2015
comment
Сценарий, который вы описываете, скорее всего, не приведет к проблемам, если вы случайно не изменили не тот конструктор в спешке, не повлияли на неправильный конструктор с помощью какого-то автоматического рефакторинга и т. Д. Потенциал проблем возрастает, если сложность сопоставления приводит к исключению общие аспекты отображения в отдельные методы. Как я уже сказал, этот конкретный тип нарушения SRP не будет таким вопиющим, как такое же нарушение в классе с бизнес-логикой. - person Derek Greer; 30.12.2015
comment
Суть сводится к следующему: написание отдельного класса сопоставления требует почти нулевых дополнительных усилий, это последовательный подход с принципами проектирования, которые, как мы надеемся, управляют остальной частью приложения, снижает шум и является подходом, который усиливает хорошие практики кодирования. . Если бы мы говорили о чем-то, имеющем реальную ценность компромисса, то это был бы другой разговор. В случаях, когда очевидного преимущества нет, я рекомендую позволить себе руководствоваться принципами дизайна. Их польза часто проявляется в непредвиденных случаях. - person Derek Greer; 30.12.2015
comment
Хороший ответ! Вот мое мнение. Если вы создадите конструктор для каждого типа domainType, вы сможете увидеть все возможные способы, которыми модель представления может / должна быть заполнена, просто взглянув на ее конструкторы. Если вы хотите изменить представление и соответствующую модель представления, вам нужно будет изменить все способы заполнения модели представления, и это будет проще, если все это будет в конструкторах модели представления. Мне это кажется более ценным и удобным в обслуживании, чем строгое соблюдение SRP. - person reasonet; 18.06.2019
comment
Я также должен был включить в статью, что добавление конструктора каждый раз, когда необходимо создать новое сопоставление, также является примером нарушения принципа открытого / закрытого типа. Опять же, модели представления, в частности, не особенно подвержены регрессии, потому что они обычно не содержат поведения и обычно не внедряются с контейнером IoC. Для других стереотипов «объект-роль» я бы не рекомендовал эту практику, а когда в большинстве случаев развязка выгодна и не требует больших затрат, я бы предпочел согласованность (то есть просто сохраняйте внешнее отображение для всех типов). - person Derek Greer; 19.06.2019