Как внедрить сразу все реализации интерфейса и выбирать нужную в рантайме?
- Как реализовать паттерн «стратегия» на Spring?
Внедрить **List<T> или Map<String, T>**: Spring передаст все бины типа. Список — в порядке @Order, карта — с именами бинов в ключах. Нужная стратегия выбирается в рантайме.
- Новая реализация — просто новый бин, код выбора не меняется
- Лучше ключ из самой стратегии (
type()), чем имя бина - Нет ни одной реализации — пустой список не падает только с
ObjectProviderилиList+ required=false
А ещё ObjectProvider<T> — ленивый доступ и stream() по всем бинам
Подробно
Задача: платежи разных типов (карта, СБП, баланс) обрабатываются разными реализациями PaymentGateway; выбор — по типу платежа в запросе.
Решение:
public interface PaymentGateway {
PaymentType type();
Receipt pay(Payment p);
}
@Service
class PaymentService {
private final Map<PaymentType, PaymentGateway> byType;
PaymentService(List<PaymentGateway> gateways) {
this.byType = gateways.stream()
.collect(Collectors.toMap(PaymentGateway::type, g -> g)); // упадёт при дубле типа — и хорошо
}
Receipt pay(Payment p) {
var g = byType.get(p.type());
if (g == null) throw new UnsupportedPaymentException(p.type());
return g.pay(p);
}
}
Варианты внедрения:
List<PaymentGateway>— все бины, порядок по@Order/Ordered;Map<String, PaymentGateway>— ключ — имя бина (хрупко при переименовании, поэтому лучше строить свою карту по ключу из стратегии);ObjectProvider<PaymentGateway>—orderedStream(),getIfAvailable(), ленивое получение.
Плюсы: открыто для расширения — новая стратегия добавляется новым бином; нет if/else по типам; каждая стратегия тестируется отдельно.
Что хотят услышать
- Внедрение List и Map всех реализаций.
- Выбор стратегии по ключу в рантайме.
- @Order и ObjectProvider — плюс.
Частые ошибки
- Ключ по имени бина, который ломается при переименовании класса.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✗Это невозможно — каждую реализацию нужно внедрять отдельным полем с @Qualifier
- ✗Пометить все реализации @Primary
- ✓Внедрить List или Map<String, PaymentGateway> и выбирать по ключу
- ✗Через ApplicationContext.getBean в каждом вызове
Что спросят дальше
- Чем @Primary отличается от @Qualifier?
- Что будет при двух @Primary?
Разбор темы
- IoC и Dependency Injection
Принцип инверсии управления, DI как его форма, три способа внедрения и почему конструктор, разрешение циклических зависимостей.
Связанные вопросы
- Как разрешить неоднозначность, когда для внедрения подходят несколько бинов одного типа? Middle
- Интерфейс PaymentGateway реализуют два бина: один помечен @Primary, другой имеет имя sbpGateway. В точке внедрения стоит @Qualifier("sbpGateway"). Какой бин будет внедрён? Middle
- Что на самом деле задаёт аннотация @Order на бине? Middle
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.