В настоящее время я работаю в среде, где большинство разработчиков являются объектно-ориентированными фанатиками. Учитывая, что мы развиваемся на 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 не мешает богатой доменной модели, отнюдь нет. Как правило, вы должны знать об инструментах, которые вы используете, и идти своим путем, а не идти по какому-то пути ради него.
