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

Как реализовывал паттерн стратегия?

2.3 Middle🔥 161 комментариев
#Многопоточность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Паттерн Strategy: мой опыт реализации

Strategy - это поведенческий паттерн проектирования, который позволяет определить семейство алгоритмов, инкапсулировать каждый и сделать их взаимозаменяемыми. Я использовал его в нескольких реальных проектах.

Что такое Strategy?

Strategy паттерн решает проблему: когда у нас есть несколько способов сделать одно и то же, но выбор способа зависит от контекста.

Без паттерна (плохо)

public class PaymentProcessor {
    public void processPayment(Order order, String paymentMethod) {
        if (paymentMethod.equals("credit_card")) {
            // Код для кредитной карты
            validateCreditCard();
            chargeCreditCard();
            logCreditCard();
            
        } else if (paymentMethod.equals("paypal")) {
            // Код для PayPal
            validatePayPalAccount();
            chargePayPal();
            logPayPal();
            
        } else if (paymentMethod.equals("crypto")) {
            // Код для крипто
            validateWallet();
            chargeCrypto();
            logCrypto();
        }
    }
}

// Проблемы:
// ❌ Огромный метод со множеством if/else
// ❌ Сложно добавлять новые методы оплаты
// ❌ Логика разных методов смешана
// ❌ Нарушает SRP и OCP (SOLID)

Решение с Strategy паттерном

Шаг 1: Определяем Strategy интерфейс

// Интерфейс, который определяет контракт для всех стратегий
public interface PaymentStrategy {
    void validate(Order order) throws ValidationException;
    void charge(Order order) throws PaymentException;
    void log(Order order, PaymentResult result);
}

Шаг 2: Реализуем конкретные стратегии

// Стратегия для кредитной карты
public class CreditCardPaymentStrategy implements PaymentStrategy {
    private CreditCardValidator validator;
    private CreditCardGateway gateway;
    private PaymentLogger logger;
    
    public CreditCardPaymentStrategy(CreditCardValidator validator, 
                                    CreditCardGateway gateway,
                                    PaymentLogger logger) {
        this.validator = validator;
        this.gateway = gateway;
        this.logger = logger;
    }
    
    @Override
    public void validate(Order order) throws ValidationException {
        Card card = order.getPaymentInfo().getCard();
        validator.validateCardNumber(card.getNumber());
        validator.validateExpiryDate(card.getExpiryDate());
        validator.validateCVV(card.getCvv());
    }
    
    @Override
    public void charge(Order order) throws PaymentException {
        Card card = order.getPaymentInfo().getCard();
        gateway.authorizePayment(order.getTotal(), card);
        gateway.capturePayment(order.getOrderId());
    }
    
    @Override
    public void log(Order order, PaymentResult result) {
        logger.log("Credit Card Payment", order.getOrderId(), result.getStatus());
    }
}

// Стратегия для PayPal
public class PayPalPaymentStrategy implements PaymentStrategy {
    private PayPalValidator validator;
    private PayPalAPI payPalAPI;
    private PaymentLogger logger;
    
    public PayPalPaymentStrategy(PayPalValidator validator,
                                 PayPalAPI payPalAPI,
                                 PaymentLogger logger) {
        this.validator = validator;
        this.payPalAPI = payPalAPI;
        this.logger = logger;
    }
    
    @Override
    public void validate(Order order) throws ValidationException {
        String email = order.getPaymentInfo().getPayPalEmail();
        validator.validateEmail(email);
        validator.validateAccountExists(email);
    }
    
    @Override
    public void charge(Order order) throws PaymentException {
        String email = order.getPaymentInfo().getPayPalEmail();
        payPalAPI.requestPayment(email, order.getTotal(), order.getOrderId());
    }
    
    @Override
    public void log(Order order, PaymentResult result) {
        logger.log("PayPal Payment", order.getOrderId(), result.getStatus());
    }
}

// Стратегия для крипто
public class CryptoPaymentStrategy implements PaymentStrategy {
    private CryptoValidator validator;
    private BlockchainAPI blockchainAPI;
    private PaymentLogger logger;
    
    public CryptoPaymentStrategy(CryptoValidator validator,
                                BlockchainAPI blockchainAPI,
                                PaymentLogger logger) {
        this.validator = validator;
        this.blockchainAPI = blockchainAPI;
        this.logger = logger;
    }
    
    @Override
    public void validate(Order order) throws ValidationException {
        String wallet = order.getPaymentInfo().getWalletAddress();
        validator.validateWalletAddress(wallet);
        validator.validateSufficientBalance(wallet, order.getTotal());
    }
    
    @Override
    public void charge(Order order) throws PaymentException {
        String wallet = order.getPaymentInfo().getWalletAddress();
        String txHash = blockchainAPI.transferFunds(
            wallet, 
            order.getTotal(),
            order.getOrderId()
        );
        blockchainAPI.waitForConfirmation(txHash);
    }
    
    @Override
    public void log(Order order, PaymentResult result) {
        logger.log("Crypto Payment", order.getOrderId(), result.getStatus());
    }
}

Шаг 3: Контекст, который использует стратегию

@Service
public class PaymentProcessor {
    private Map<String, PaymentStrategy> strategies = new HashMap<>();
    
    // Dependency Injection
    public PaymentProcessor(CreditCardPaymentStrategy ccStrategy,
                           PayPalPaymentStrategy ppStrategy,
                           CryptoPaymentStrategy cryptoStrategy) {
        strategies.put("credit_card", ccStrategy);
        strategies.put("paypal", ppStrategy);
        strategies.put("crypto", cryptoStrategy);
    }
    
    // Альтернатива с Factory
    public void registerStrategy(String type, PaymentStrategy strategy) {
        strategies.put(type, strategy);
    }
    
    // Главный метод обработки платежа
    public PaymentResult processPayment(Order order, String paymentMethodType) 
            throws PaymentException {
        
        // Получаем правильную стратегию
        PaymentStrategy strategy = strategies.get(paymentMethodType);
        
        if (strategy == null) {
            throw new UnknownPaymentMethodException(
                "Unknown payment method: " + paymentMethodType
            );
        }
        
        try {
            // [1] Валидация
            strategy.validate(order);
            
            // [2] Обработка платежа
            strategy.charge(order);
            
            // [3] Логирование
            PaymentResult result = new PaymentResult(
                order.getOrderId(),
                PaymentStatus.SUCCESS,
                paymentMethodType
            );
            strategy.log(order, result);
            
            return result;
            
        } catch (ValidationException e) {
            throw new PaymentException("Payment validation failed: " + e.getMessage(), e);
        } catch (PaymentException e) {
            throw e;
        }
    }
}

Использование в контроллере

@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
    private final PaymentProcessor paymentProcessor;
    
    @PostMapping("/{orderId}/pay")
    public ResponseEntity<PaymentResponse> payOrder(
            @PathVariable Long orderId,
            @RequestBody PaymentRequest request) {
        
        try {
            // Получаем заказ
            Order order = orderService.getOrder(orderId);
            
            // Обрабатываем платёж (стратегия выбирается автоматически)
            PaymentResult result = paymentProcessor.processPayment(
                order, 
                request.getPaymentMethod()  // "credit_card", "paypal", "crypto"
            );
            
            // Обновляем заказ
            order.markAsPaid(result);
            orderService.save(order);
            
            return ResponseEntity.ok(new PaymentResponse(
                result.getOrderId(),
                result.getStatus()
            ));
            
        } catch (PaymentException e) {
            return ResponseEntity.status(400)
                .body(new PaymentResponse(orderId, PaymentStatus.FAILED));
        }
    }
}

Конфигурация Spring для DI

@Configuration
public class PaymentConfig {
    
    @Bean
    public CreditCardValidator creditCardValidator() {
        return new CreditCardValidator();
    }
    
    @Bean
    public CreditCardGateway creditCardGateway() {
        return new StripeGateway();  // Например, Stripe
    }
    
    @Bean
    public PaymentLogger paymentLogger() {
        return new DatabasePaymentLogger();
    }
    
    // Регистрируем стратегии как бины
    
    @Bean
    public CreditCardPaymentStrategy creditCardPaymentStrategy(
            CreditCardValidator validator,
            CreditCardGateway gateway,
            PaymentLogger logger) {
        return new CreditCardPaymentStrategy(validator, gateway, logger);
    }
    
    @Bean
    public PayPalPaymentStrategy payPalPaymentStrategy(
            PayPalValidator validator,
            PayPalAPI api,
            PaymentLogger logger) {
        return new PayPalPaymentStrategy(validator, api, logger);
    }
    
    @Bean
    public CryptoPaymentStrategy cryptoPaymentStrategy(
            CryptoValidator validator,
            BlockchainAPI api,
            PaymentLogger logger) {
        return new CryptoPaymentStrategy(validator, api, logger);
    }
    
    @Bean
    public PaymentProcessor paymentProcessor(
            CreditCardPaymentStrategy cc,
            PayPalPaymentStrategy pp,
            CryptoPaymentStrategy crypto) {
        return new PaymentProcessor(cc, pp, crypto);
    }
}

Преимущества решения

// ✅ OCP (Open/Closed Principle)
// Класс открыт для расширения (можем добавить новую стратегию)
// Закрыт для модификации (не меняем PaymentProcessor)

// ✅ SRP (Single Responsibility)
// Каждая стратегия отвечает только за один метод оплаты

// ✅ DIP (Dependency Inversion)
// PaymentProcessor зависит от интерфейса, а не от конкретных реализаций

// ✅ Легко тестировать
public class PaymentProcessorTest {
    @Test
    void testCreditCardPayment() {
        // Mock стратегию
        PaymentStrategy mockStrategy = mock(PaymentStrategy.class);
        
        PaymentProcessor processor = new PaymentProcessor(mockStrategy, null, null);
        // Тестируем
    }
}

Добавление новой стратегии

Чтобы добавить новый метод оплаты (например, Apple Pay):

// 1. Создаём новую стратегию
public class ApplePayStrategy implements PaymentStrategy {
    // Реализуем методы
}

// 2. Регистрируем в конфигурации
@Bean
public ApplePayStrategy applePayStrategy(...) {
    return new ApplePayStrategy(...);
}

// 3. Добавляем в PaymentProcessor
// (если используем список)
paymentProcessor.registerStrategy("apple_pay", applePayStrategy);

// 4. Всё работает!
// Никаких изменений в основном коде PaymentProcessor

Реальный пример из моего проекта

В одном проекте я использовал Strategy для разных алгоритмов сортировки результатов поиска:

public interface SearchRankingStrategy {
    List<SearchResult> rank(List<SearchResult> results, SearchQuery query);
}

// Стратегия 1: По релевантности (TF-IDF)
public class RelevanceRankingStrategy implements SearchRankingStrategy { ... }

// Стратегия 2: По популярности
public class PopularityRankingStrategy implements SearchRankingStrategy { ... }

// Стратегия 3: По дате
public class RecencyRankingStrategy implements SearchRankingStrategy { ... }

// Используем в сервисе
@Service
public class SearchService {
    public List<SearchResult> search(String query, String rankingType) {
        SearchRankingStrategy strategy = strategies.get(rankingType);
        List<SearchResult> results = elasticSearch.search(query);
        return strategy.rank(results, query);
    }
}

Когда использовать Strategy

✓ Несколько способов выполнить задачу
✓ Алгоритм может меняться в runtime
✓ Хотим избежать больших if/else/switch блоков
✓ Каждый алгоритм независим
✓ Нужна возможность добавлять новые алгоритмы

✗ Только один способ сделать что-то
✗ Алгоритмы редко меняются
✗ Много общего кода между алгоритмами (лучше Template Method)

Резюме

Strategy паттерн - один из самых полезных паттернов, который я использую регулярно:

  1. Определяем интерфейс с контрактом алгоритма
  2. Создаём конкретные реализации для каждого случая
  3. Используем Map/Factory для выбора нужной стратегии
  4. Контекст работает с интерфейсом, а не с конкретными классами

Это делает код чище, более maintainable и соответствует SOLID принципам. Я нашёл это особенно полезным в платёжных системах, системах рекомендаций и при работе с разными источниками данных.