← Назад к вопросам

Как правильно организовать три зависимости внутри сервиса, каждая из которых имеет интерфейс и три реализации

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 принципа.