Как избежать раздутых доменных объектов

12

Мы пытаемся переместить данные из нашего раздутого уровня Service в наш уровень Domain, используя подход DDD. В настоящее время в наших сервисах много бизнес-логики, которая распространена повсеместно и не получает наследства.

У нас есть центральный класс Domain, который находится в центре большинства нашей работы - Trade. Объект Trade будет знать, как оценивать себя, как оценивать риск, подтверждать себя и т. Д. Затем мы можем заменить условные выражения полиморфизмом. Например: SimpleTrade будет оценивать себя в одну сторону, а ComplexTrade - в другую.

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

Итак, у нас есть выбор:

  1. Поместите логику обработки в класс Trade. Логика обработки теперь полиморфна в зависимости от типа сделки, но класс Trade теперь имеет несколько функций ответственности (ценообразование, риск и т. Д.) И является большим
  2. Поместите логику обработки в другой класс, такой как TradePricingService. Больше не полиморфен с деревом наследования Trade, но классы меньше и их проще тестировать.

Какой будет предложенный подход?

djcredo
источник
Нет проблем - я рад принять миграцию!
1
Остерегайтесь обратного: martinfowler.com/bliki/AnemicDomainModel.html
TrueWill
1
«Размер класса будет увеличиваться экспоненциально по мере добавления новых функций» - любой программист должен знать лучше, чем неправильно использовать слово «экспоненциально».
Майкл Боргвардт
@Piskvor это просто глупо
Арнис Лапса
@Arnis L .: Спасибо за ваш вдумчивый комментарий. Обратите внимание, что это было, цитата, «перенесено из stackoverflow.com 22 ноября в 22:19», вместе с моим комментарием оттуда. Теперь я удалил свой комментарий о том, что "это было бы лучше для программистов. SE"; Теперь, у вас есть что добавить, или это была единственная идея, которую вы хотели выразить?
Писквор покинул здание

Ответы:

8

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

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

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

Терри Уилкокс
источник
Я согласен. Думая об объектах с точки зрения поведения, которое формирует решение (Pricing, RiskAssessment), а не пытаясь смоделировать проблему, избегает этих монолитных классов.
Гаррет Холл
Тоже согласен. Композиция, множество небольших классов со специфическими, одиночными обязанностями, ограниченное использование частных методов, множество удачных интерфейсов и т. Д.
Ян
4

Ваш вопрос определенно заставляет меня думать о шаблоне стратегии . Затем вы можете поменять различные стратегии торговли / ценообразования, аналогичные тому, что вы называете TradePricingService.

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

Скотт Уитлок
источник
2

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

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

interface IPriceCalculator {
  double getPrice(ITrader t);
}
interface ITrader {
  IPriceCalculator getPriceCalculator();
}
class Tracer implements ITrader {
  private IPriceCalculator myPriceCalculator = null;
  IPriceCalculator getPriceCalculator() {
    if (myPriceCalculator == null)
      myPriceCalculator = PriceCalculatorFactory.get(this);
    return myPriceCalculator;
  }
}

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

Я обычно стараюсь держать классы адаптера - например, подклассы IPriceCalculatorвыше - без сохранения состояния. Т.е. эти классы не должны содержать локальных данных, если это возможно, чтобы уменьшить количество экземпляров, которые должны быть созданы. Поэтому я обычно предоставляю основной адаптированный объект в качестве аргумента во всех методах - как getPrice(ITrader)описано выше.

Тонни Мэдсен
источник
2

не могу много сказать о вашем домене, но

У нас есть центральный класс Domain, который находится в центре большинства нашей работы - Trade.

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

Может быть, RiskEvaluation может быть отдельной сущностью в вашем домене, в конечном итоге с определенным жизненным циклом (я не могу сказать, я просто спекулирую ... вы знаете свой домен, я не знаю), но ключ заключается в создании неявных понятий явное и во избежание связи, которая не обусловлена ​​поведением, а только устаревшей связью данных

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

ZioBrando
источник