← Назад к вопросам
Как правильно организовать три зависимости внутри сервиса, каждая из которых имеет интерфейс и три реализации
2.0 Middle🔥 111 комментариев
#Другое
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Организация нескольких реализаций зависимостей в сервисе
Когда у одного интерфейса есть несколько реализаций, перед нами встает задача правильно инджектить нужную реализацию в нужное место. Это ключевая часть проектирования сервиса. Рассмотрим несколько подходов:
1. Использование квалификаторов (Qualifier) - самый распространенный подход
Это работает отлично, если у вас есть Spring (или другой DI контейнер):
// Интерфейс
public interface PaymentProcessor {
void processPayment(Payment payment);
}
// Три реализации
@Component("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {
System.out.println("Processing via PayPal");
}
}
@Component("stripeProcessor")
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {
System.out.println("Processing via Stripe");
}
}
@Component("paymentGatewayProcessor")
public class PaymentGatewayProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {
System.out.println("Processing via Payment Gateway");
}
}
// Сервис с тремя зависимостями
@Service
public class OrderService {
private final PaymentProcessor paypalProcessor;
private final PaymentProcessor stripeProcessor;
private final PaymentProcessor gatewayProcessor;
public OrderService(
@Qualifier("paypalProcessor") PaymentProcessor paypal,
@Qualifier("stripeProcessor") PaymentProcessor stripe,
@Qualifier("paymentGatewayProcessor") PaymentProcessor gateway
) {
this.paypalProcessor = paypal;
this.stripeProcessor = stripe;
this.gatewayProcessor = gateway;
}
public void processOrder(Order order) {
if (order.getPaymentMethod() == PaymentMethod.PAYPAL) {
paypalProcessor.processPayment(order.getPayment());
} else if (order.getPaymentMethod() == PaymentMethod.STRIPE) {
stripeProcessor.processPayment(order.getPayment());
} else {
gatewayProcessor.processPayment(order.getPayment());
}
}
}
2. Использование Map для динамического выбора - более гибкий подход
Этот подход удобен, когда количество реализаций может расти:
// Конфигурация с регистрацией всех реализаций
@Configuration
public class PaymentProcessorConfig {
@Bean
public Map<String, PaymentProcessor> paymentProcessors(
PayPalPaymentProcessor paypal,
StripePaymentProcessor stripe,
PaymentGatewayProcessor gateway
) {
Map<String, PaymentProcessor> processors = new HashMap<>();
processors.put("paypal", paypal);
processors.put("stripe", stripe);
processors.put("gateway", gateway);
return processors;
}
}
// Сервис использует Map
@Service
public class OrderService {
private final Map<String, PaymentProcessor> paymentProcessors;
public OrderService(Map<String, PaymentProcessor> paymentProcessors) {
this.paymentProcessors = paymentProcessors;
}
public void processOrder(Order order) {
PaymentProcessor processor = paymentProcessors.get(
order.getPaymentMethod().toLowerCase()
);
if (processor != null) {
processor.processPayment(order.getPayment());
} else {
throw new PaymentProcessorNotFoundException(
"No processor for: " + order.getPaymentMethod()
);
}
}
}
3. Использование Strategy паттерна с Factory
Если вы не используете Spring, или хотите явно контролировать создание объектов:
// Enum с логикой выбора
public enum PaymentMethodType {
PAYPAL("paypal"),
STRIPE("stripe"),
GATEWAY("gateway");
private final String code;
PaymentMethodType(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public static PaymentMethodType fromCode(String code) {
return Arrays.stream(values())
.filter(t -> t.code.equals(code))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown type: " + code));
}
}
// Factory для создания процессоров
public class PaymentProcessorFactory {
private final PayPalPaymentProcessor paypalProcessor;
private final StripePaymentProcessor stripeProcessor;
private final PaymentGatewayProcessor gatewayProcessor;
public PaymentProcessorFactory(
PayPalPaymentProcessor paypal,
StripePaymentProcessor stripe,
PaymentGatewayProcessor gateway
) {
this.paypalProcessor = paypal;
this.stripeProcessor = stripe;
this.gatewayProcessor = gateway;
}
public PaymentProcessor getProcessor(PaymentMethodType type) {
return switch (type) {
case PAYPAL -> paypalProcessor;
case STRIPE -> stripeProcessor;
case GATEWAY -> gatewayProcessor;
};
}
}
// Сервис использует factory
@Service
public class OrderService {
private final PaymentProcessorFactory factory;
public OrderService(PaymentProcessorFactory factory) {
this.factory = factory;
}
public void processOrder(Order order) {
PaymentMethodType type = PaymentMethodType.fromCode(
order.getPaymentMethod()
);
PaymentProcessor processor = factory.getProcessor(type);
processor.processPayment(order.getPayment());
}
}
4. Использование @Primary для значения по умолчанию
Если одна из реализаций должна использоваться по умолчанию:
@Component("paypalProcessor")
public class PayPalPaymentProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {
System.out.println("PayPal");
}
}
@Component("stripeProcessor")
@Primary // Эта реализация будет выбрана по умолчанию
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {
System.out.println("Stripe");
}
}
// В сервисе можно инджектить без квалификатора
@Service
public class OrderService {
private final PaymentProcessor defaultProcessor; // Stripe
public OrderService(PaymentProcessor processor) {
this.defaultProcessor = processor;
}
}
5. Комбинированный подход - рекомендуемый
Для действительно гибкой и масштабируемой системы:
// Interface с аннотацией для типизации
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface PaymentMethod {
String value();
}
// Реализации с маркерами
@Component
@PaymentMethod("paypal")
public class PayPalPaymentProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {}
}
@Component
@PaymentMethod("stripe")
public class StripePaymentProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {}
}
@Component
@PaymentMethod("gateway")
public class PaymentGatewayProcessor implements PaymentProcessor {
@Override
public void processPayment(Payment payment) {}
}
// Factory с поддержкой сканирования
@Component
public class PaymentProcessorRegistry {
private final Map<String, PaymentProcessor> processors = new ConcurrentHashMap<>();
public PaymentProcessorRegistry(List<PaymentProcessor> allProcessors) {
allProcessors.forEach(processor -> {
PaymentMethod annotation = processor.getClass()
.getAnnotation(PaymentMethod.class);
if (annotation != null) {
processors.put(annotation.value(), processor);
}
});
}
public PaymentProcessor getProcessor(String method) {
PaymentProcessor processor = processors.get(method);
if (processor == null) {
throw new PaymentProcessorNotFoundException(
"No processor for: " + method
);
}
return processor;
}
}
// Финальный сервис
@Service
public class OrderService {
private final PaymentProcessorRegistry registry;
public OrderService(PaymentProcessorRegistry registry) {
this.registry = registry;
}
public void processOrder(Order order) {
PaymentProcessor processor = registry.getProcessor(
order.getPaymentMethod()
);
processor.processPayment(order.getPayment());
}
}
Сравнение подходов
| Подход | Плюсы | Минусы | Когда использовать |
|---|---|---|---|
| Qualifier | Простой, явный | Много кода для 3+ реализаций | 2-3 реализации |
| Map | Гибкий, масштабируемый | Нужна конфигурация | 3+ реализации |
| Factory | Явная логика выбора | Extra класс | Сложная логика выбора |
| @Primary | Минимум кода | Только для одного дефолта | Одна главная реализация |
| Registry | Максимальная гибкость | Сложнее в отладке | Динамическая регистрация |
Рекомендация
Для трех зависимостей в одном сервисе лучше всего подходит подход с Map или Registry:
- Он явно показывает намерение - что здесь несколько вариантов
- Легко добавлять новые реализации без изменения сервиса
- Уменьшает coupling - сервис не знает о конкретных реализациях
- Более testable - просто передать Mock в Map
Это отличный пример применения принципов SOLID, особенно Open/Closed принципа.