В моем приложении Spring Boot у меня есть понятие Stage и StageProcessor, которое обрабатывает Stage. Stage имеет свойство StageType enum. У меня есть разные реализации интерфейса StageProcessor, и эти реализации являются компонентами Spring. Теперь у меня есть еще один компонент Spring, WorkflowProcessor, который должен вызывать соответствующий StageProcessor в зависимости от StageType из Stage. До сих пор я пришел к следующему:
@Service
public class StageConfig {
@Autowired
private StageProcessorA stageProcessorA;
@Autowired
private StageProcessorB stageProcessorB;
public StageProcessor getProcessor(Stage stage) {
switch(stage.getType()) {
case A:
return stageProcessorA;
break;
case B:
return stageProcessorB;
break;
}
}
}
Интересно, не хватает ли мне какого-либо шаблона проектирования или механизма Spring. Есть идеи по лучшему дизайну?
Является ли Stage интерфейсом? Это фасоль? Есть только один? Что определяет, будет ли это А или В?
Stage не является ни интерфейсом, ни bean-компонентом. Это сущность. StageType определяет, какой процессор использовать.
Ты не понял. Что определяет, будет ли stage.getType() A или B?
Это просто бизнес-данные, ранее сохраненные в БД.




Это немного зависит от специфики вашего случая. Во многих случаях этапы на самом деле не жестко закодированы, как в перечислении, а настраиваются для различных систем. Кроме того, чем больше потенциальных этапов у вас есть, тем больше окупается более подробная настройка, но избегающая повторений.
В общем, я бы порекомендовал здесь шаблон распознавателя Spring. Код выглядит примерно так, где KeyType обычно это перечисление или строка. Общая идея заключается в том, что каждая реализация сообщает вам, какие вещи (этапы, типы, параметры и т. д.) она может обрабатывать, а затем вы ищете соответствие. (Вариант, в котором нет прямого поиска сопоставления, состоит в том, чтобы иметь boolean canHandle(something) и повторять, пока не найдете его.)
interface StageProcessor {
OutputType process(Stage stage);
KeyType stageKey();
}
@Service
class StageProcessors {
Map<KeyType, StageProcessor> stageProcessors;
StageProcessors(Collection<StageProcessor> processors) {
stageProcessors = processors.stream().collect(groupingBy(StageProcessor::stageKey));
assert stageProcessors.size() == expectedNumberOfProcessors;
}
StageProcessor getProcessor(KeyType stage) {
// although usually your service would take care of dispatching directly
return stageProcessors.get(stage);
}
}
(И в качестве примечания: Избегайте инъекций поля.)
Это определенно более чистый дизайн. Я буду использовать его, дополненный идеями @GabiM. Что касается внедрения в поле, это просто для более чистого кода прототипа, я буду придерживаться внедрения конструктора в реальном коде. Спасибо!
Это полностью основано на идее @chrylis, просто процессорам не нужно менять свой API для этого, вы можете просто сделать это с помощью аннотации. Также бывает случай, когда у вас несколько процессоров для одного типа.
interface StageProcessor {
OutputType process(Stage stage);
}
@Component
@Processes(StageType.A)
class StageProcessorA implements StageProcessor{
OutputType process(Stage stage){
//validate stage is StageType.A
}
}
@interface Processes{
StageType type;
StageType getType(){
return type;
}
}
@Component
@Processes(StageType.B)
class StageProcessorB implements StageProcessor{
}
@Service
class StageProcessors {
Map<StageType, List<StageProcessor>> stageProcessors;
StageProcessors(Collection<StageProcessor> processors) {
Map<StageType, List<StageProcessor>> map = new HashMap<>();
for (StageProcessor processor : processors) {
StageType stageType = processor.getClass().getAnnotation(Processes.class).getType();
map.computeIfAbsent(stageType, k -> new ArrayList<>()).add(processor);
}
stageProcessors = map;
assert stageProcessors.size() == expectedNumberOfProcessors;
}
List<StageProcessor> getProcessors(StageType stage) {
return stageProcessors.get(stage);
}
}
Хорошо, что я бы сделал, это использовать ApplicationContextAware, а затем вернуть правильный объект методом getBean.