В настоящее время я работаю в среде, где большинство разработчиков являются объектно-ориентированными фанатиками. Учитывая, что мы развиваемся на Java, я думаю, что это хорошая вещь — сохранить часть фанатиков. В частности, я столкнулся с глубоко укоренившимся мемом, в котором говорится, что моделирование Rich Domain Objects и одновременное использование Spring-зависимостей невозможно. Это не только совершенно неверно, но и показывает недостаток знаний о функциях Spring, которые я попытаюсь исправить в этой статье.
Тем не менее, моя главная мысль не о Spring, а о какой-либо парадигме, которая наиболее ценится, будь то объектно-ориентированное программирование, функциональное программирование, аспектно-ориентированное программирование или что-то еще. Они предназначены только для того, чтобы придать программам желаемые свойства: тестируемые на модуле, читаемые, что угодно … Поэтому всегда следует сосредоточиться на этих свойствах, а не на способе их получения. Помнить:
Когда мудрец указывает на луну, идиот смотрит на палец.
Вернемся к проблеме под рукой. Основная идея состоит в том, чтобы какой-то компонент имел ссылку на какой-либо сервис для выполнения каких-то задач (обратите внимание на реальные слова, стоящие за этой идеей…), чтобы вы могли вызвать:
someBean.doStuff()
Вот что именно не нужно делать:
public class TopCaller { @Autowired private StuffService stuffService; public SomeBean newSomeBean() { return new SomeBeanBuilder().with(stuffService).build(); } }
Эта конструкция должна быть проклятием для разработчиков , способствующих юнит-тестирование как новые () глубоко подсоединяет
TopCaller
иSomeBeanBuilder
классов. Нет возможности заглушить эту зависимость в тесте, она не позволяет тестироватьcreateSomeBean()
метод изолированно.Что вам нужно, это ввести
SomeBean
прототипы в
SomeBeanBuilder
синглтон. Это внедрение метода и возможно в Spring с помощью методов поиска (я уже писал об этом некоторое время назад, и вам, вероятно, стоит взглянуть на это).
public abstract class TopCaller { @Autowired private StuffService stuffService; public SomeBean newSomeBean() { return newSomeBeanBuilder().with(stuffService).build(); } public abstract SomeBeanBuilder newSomeBeanBuilder(); }
С правильной конфигурацией Spring будет заботиться о предоставлении разных значений при
SomeBeanBuilder
каждомnewSomeBeanBuilder()
вызове метода. С этим новым дизайном мы изменили сильную связь междуTopCaller
иSomeBeanBuilder
на мягкую связь, которая может быть заглушена в тестах и позволяет проводить модульное тестирование.В текущем проекте, по- видимому, единственная причина для SomeBeanBuilder
существовать, чтобы пройти StuffService
от TopCaller
к SomeBean
экземплярам. Нет необходимости хранить его методом инъекции.
Есть два возможных улучшения:
- Учитывая наши «новые» знания, введите:
- Способ
newSomeBean()
вместо того , чтобыnewSomeBeanBuilder()
вTopCaller
StuffService
прямо вSomeBean
- Способ
- Сохранить
StuffService
какTopCaller
атрибут и передавать его каждый раз, когдаdoStuff()
вызывается
Я предпочел бы второй вариант, так как я хмурился на объекте предметной области, хранящем ссылки на одноэлементные сервисы, если только нет большого количества параметров для передачи при каждом вызове. Как всегда, нет ни одного лучшего выбора, а только контекстного выбора.
Кроме того, я бы также использовал явное управление зависимостями вместо каких-то автоматических вещей, но это еще одна дискуссия.
Я надеюсь, что этот кусок доказал вне всякого сомнения, что Spring не мешает богатой доменной модели, отнюдь нет. Как правило, вы должны знать об инструментах, которые вы используете, и идти своим путем, а не идти по какому-то пути ради него.