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

Как бы создал шаблон Factory

2.0 Middle🔥 171 комментариев
#SOLID и паттерны проектирования

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

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

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

# Создание шаблона Factory: Step-by-Step

Я покажу, как создать гибкий и масштабируемый Factory паттерн, следуя лучшим практикам.

Сценарий

Представим, что нужно создать разные типы платёжных систем:

  • Кредитная карта
  • PayPal
  • Криптовалюта
  • Apple Pay

Нужно, чтобы было легко добавлять новые типы платежей.

Шаг 1: Определить интерфейс (Contract)

// Общий контракт для всех платёжных систем
public interface PaymentProcessor {
    /**
     * Обрабатывает платёж
     * @param payment платёж для обработки
     * @return результат обработки
     * @throws PaymentException если произошла ошибка
     */
    PaymentResult process(Payment payment) throws PaymentException;
    
    /**
     * Проверяет, поддерживается ли эта система платежей
     * @param paymentType тип платежа
     * @return true если поддерживается
     */
    boolean supports(PaymentType paymentType);
}

// Enum для типов платежей
public enum PaymentType {
    CREDIT_CARD,
    PAYPAL,
    CRYPTO,
    APPLE_PAY
}

// DTO для результата
public class PaymentResult {
    private final UUID transactionId;
    private final PaymentStatus status;
    private final String message;
    
    // constructor, getters
}

Шаг 2: Реализовать конкретные процессоры

// 1. Кредитная карта
public class CreditCardPaymentProcessor implements PaymentProcessor {
    private final CardGateway cardGateway;
    
    public CreditCardPaymentProcessor(CardGateway cardGateway) {
        this.cardGateway = cardGateway;
    }
    
    @Override
    public PaymentResult process(Payment payment) throws PaymentException {
        try {
            String cardToken = encryptCardData(payment.getCardData());
            String response = cardGateway.charge(
                payment.getAmount(),
                cardToken
            );
            return new PaymentResult(
                UUID.randomUUID(),
                PaymentStatus.SUCCESS,
                response
            );
        } catch (GatewayException e) {
            throw new PaymentException("Card processing failed", e);
        }
    }
    
    @Override
    public boolean supports(PaymentType type) {
        return type == PaymentType.CREDIT_CARD;
    }
    
    private String encryptCardData(CardData data) {
        // encryption logic
        return "encrypted_token";
    }
}

// 2. PayPal
public class PayPalPaymentProcessor implements PaymentProcessor {
    private final PayPalApi paypalApi;
    
    public PayPalPaymentProcessor(PayPalApi paypalApi) {
        this.paypalApi = paypalApi;
    }
    
    @Override
    public PaymentResult process(Payment payment) throws PaymentException {
        try {
            PayPalTransaction tx = paypalApi.createTransaction(
                payment.getPaypalEmail(),
                payment.getAmount()
            );
            return new PaymentResult(
                tx.getId(),
                PaymentStatus.SUCCESS,
                tx.getDetails()
            );
        } catch (PayPalException e) {
            throw new PaymentException("PayPal processing failed", e);
        }
    }
    
    @Override
    public boolean supports(PaymentType type) {
        return type == PaymentType.PAYPAL;
    }
}

// 3. Криптовалюта
public class CryptoPaymentProcessor implements PaymentProcessor {
    private final BlockchainClient blockchain;
    
    public CryptoPaymentProcessor(BlockchainClient blockchain) {
        this.blockchain = blockchain;
    }
    
    @Override
    public PaymentResult process(Payment payment) throws PaymentException {
        try {
            String txHash = blockchain.transfer(
                payment.getFromAddress(),
                payment.getToAddress(),
                payment.getAmount()
            );
            return new PaymentResult(
                UUID.randomUUID(),
                PaymentStatus.PENDING, // Может занять время
                "Hash: " + txHash
            );
        } catch (BlockchainException e) {
            throw new PaymentException("Crypto processing failed", e);
        }
    }
    
    @Override
    public boolean supports(PaymentType type) {
        return type == PaymentType.CRYPTO;
    }
}

Шаг 3: Создать Factory (Вариант 1: Simple Factory)

// Простая фабрика — хороша для небольшого числа типов
public class PaymentProcessorFactory {
    private final CardGateway cardGateway;
    private final PayPalApi paypalApi;
    private final BlockchainClient blockchain;
    
    public PaymentProcessorFactory(
            CardGateway cardGateway,
            PayPalApi paypalApi,
            BlockchainClient blockchain) {
        this.cardGateway = cardGateway;
        this.paypalApi = paypalApi;
        this.blockchain = blockchain;
    }
    
    // Основной метод фабрики
    public PaymentProcessor create(PaymentType type) {
        return switch(type) {
            case CREDIT_CARD -> new CreditCardPaymentProcessor(cardGateway);
            case PAYPAL -> new PayPalPaymentProcessor(paypalApi);
            case CRYPTO -> new CryptoPaymentProcessor(blockchain);
            case APPLE_PAY -> new ApplePayPaymentProcessor(cardGateway);
            default -> throw new IllegalArgumentException(
                "Unknown payment type: " + type
            );
        };
    }
}

Шаг 4: Использовать Factory

@Service
public class OrderService {
    private final PaymentProcessorFactory paymentFactory;
    private final OrderRepository orderRepository;
    
    public OrderService(
            PaymentProcessorFactory paymentFactory,
            OrderRepository orderRepository) {
        this.paymentFactory = paymentFactory;
        this.orderRepository = orderRepository;
    }
    
    public void processOrder(Order order, PaymentType paymentType)
            throws PaymentException {
        
        // Получаем подходящий процессор
        PaymentProcessor processor = paymentFactory.create(paymentType);
        
        // Обрабатываем платёж
        Payment payment = mapOrderToPayment(order);
        PaymentResult result = processor.process(payment);
        
        // Сохраняем результат
        order.setPaymentStatus(result.getStatus());
        order.setTransactionId(result.getTransactionId());
        orderRepository.save(order);
    }
    
    private Payment mapOrderToPayment(Order order) {
        // mapping logic
        return new Payment();
    }
}

Шаг 5: Улучшенная версия (Вариант 2: Registry Factory)

Для большего числа типов — лучше использовать Registry:

public class PaymentProcessorRegistry {
    private final Map<PaymentType, PaymentProcessor> processors;
    
    public PaymentProcessorRegistry(
            List<PaymentProcessor> processorList) {
        this.processors = new EnumMap<>(PaymentType.class);
        for (PaymentProcessor processor : processorList) {
            for (PaymentType type : PaymentType.values()) {
                if (processor.supports(type)) {
                    processors.put(type, processor);
                }
            }
        }
    }
    
    public PaymentProcessor get(PaymentType type) {
        return Optional.ofNullable(processors.get(type))
            .orElseThrow(() -> 
                new IllegalArgumentException(
                    "No processor for type: " + type
                )
            );
    }
    
    public boolean isSupported(PaymentType type) {
        return processors.containsKey(type);
    }
}

Использование Registry:

@Service
public class OrderService {
    private final PaymentProcessorRegistry registry;
    
    public OrderService(PaymentProcessorRegistry registry) {
        this.registry = registry;
    }
    
    public void processOrder(Order order, PaymentType paymentType)
            throws PaymentException {
        
        PaymentProcessor processor = registry.get(paymentType);
        PaymentResult result = processor.process(mapToPayment(order));
        
        order.setPaymentStatus(result.getStatus());
        orderRepository.save(order);
    }
}

Шаг 6: Spring интеграция

@Configuration
public class PaymentProcessorConfig {
    
    @Bean
    public CardGateway cardGateway() {
        return new CardGatewayImpl();
    }
    
    @Bean
    public PayPalApi paypalApi() {
        return new PayPalApiImpl();
    }
    
    @Bean
    public BlockchainClient blockchain() {
        return new BlockchainClientImpl();
    }
    
    // Вариант 1: SimpleFactory
    @Bean
    public PaymentProcessorFactory paymentFactory(
            CardGateway cardGateway,
            PayPalApi paypalApi,
            BlockchainClient blockchain) {
        return new PaymentProcessorFactory(
            cardGateway,
            paypalApi,
            blockchain
        );
    }
    
    // Вариант 2: Registry (более масштабируемо)
    @Bean
    public PaymentProcessorRegistry paymentRegistry(
            CardGateway cardGateway,
            PayPalApi paypalApi,
            BlockchainClient blockchain) {
        
        return new PaymentProcessorRegistry(
            List.of(
                new CreditCardPaymentProcessor(cardGateway),
                new PayPalPaymentProcessor(paypalApi),
                new CryptoPaymentProcessor(blockchain),
                new ApplePayPaymentProcessor(cardGateway)
            )
        );
    }
}

Шаг 7: Тестирование Factory

@SpringBootTest
class PaymentProcessorFactoryTest {
    
    @MockBean
    private CardGateway cardGateway;
    
    @MockBean
    private PayPalApi paypalApi;
    
    @MockBean
    private BlockchainClient blockchain;
    
    @Autowired
    private PaymentProcessorFactory factory;
    
    @Test
    void testCreateCreditCardProcessor() {
        PaymentProcessor processor = factory.create(PaymentType.CREDIT_CARD);
        
        assertThat(processor)
            .isInstanceOf(CreditCardPaymentProcessor.class);
        assertTrue(processor.supports(PaymentType.CREDIT_CARD));
        assertFalse(processor.supports(PaymentType.PAYPAL));
    }
    
    @Test
    void testCreatePayPalProcessor() {
        PaymentProcessor processor = factory.create(PaymentType.PAYPAL);
        
        assertThat(processor)
            .isInstanceOf(PayPalPaymentProcessor.class);
    }
    
    @Test
    void testUnknownType() {
        assertThrows(IllegalArgumentException.class, () -> {
            factory.create(null);
        });
    }
}

Шаг 8: Интеграционное тестирование

@SpringBootTest
class OrderServiceIntegrationTest {
    
    @Autowired
    private OrderService orderService;
    
    @MockBean
    private CardGateway cardGateway;
    
    @Test
    void testProcessOrderWithCreditCard() throws PaymentException {
        // Arrange
        Order order = createTestOrder();
        when(cardGateway.charge(any(), any()))
            .thenReturn("SUCCESS");
        
        // Act
        orderService.processOrder(order, PaymentType.CREDIT_CARD);
        
        // Assert
        verify(cardGateway).charge(any(), any());
        assertThat(order.getPaymentStatus()).isEqualTo(
            PaymentStatus.SUCCESS
        );
    }
}

Преимущества этого подхода

Разделение ответственности — каждый процессор отвечает за свой тип
Легко добавлять новые типы — просто создай новый класс
Dependency injection — легко тестировать и мокировать
Открыто/Закрыто — открыто для расширения, закрыто для модификации
Type-safe — используем PaymentType enum вместо строк
Централизованное управление — все в одном месте

Как добавить новый тип платежа

Допустим, нужно добавить Google Pay:

// 1. Добавить в enum
public enum PaymentType {
    CREDIT_CARD,
    PAYPAL,
    CRYPTO,
    APPLE_PAY,
    GOOGLE_PAY; // Новый
}

// 2. Создать реализацию
public class GooglePayPaymentProcessor implements PaymentProcessor {
    private final GooglePayApi googlePayApi;
    
    public GooglePayPaymentProcessor(GooglePayApi googlePayApi) {
        this.googlePayApi = googlePayApi;
    }
    
    @Override
    public PaymentResult process(Payment payment) 
            throws PaymentException {
        // implementation
    }
    
    @Override
    public boolean supports(PaymentType type) {
        return type == PaymentType.GOOGLE_PAY;
    }
}

// 3. Обновить конфигурацию
@Bean
public PaymentProcessorRegistry paymentRegistry(...) {
    return new PaymentProcessorRegistry(
        List.of(
            // ...
            new GooglePayPaymentProcessor(googlePayApi) // Добавили
        )
    );
}

Best Practices

1. Используй Interface для Factory

public interface PaymentProcessorFactory {
    PaymentProcessor create(PaymentType type);
}

2. Обработка ошибок

public PaymentProcessor create(PaymentType type) 
        throws PaymentProcessorException {
    return switch(type) {
        case CREDIT_CARD -> new CreditCardPaymentProcessor(gateway);
        default -> throw new PaymentProcessorException(
            "Unsupported payment type: " + type
        );
    };
}

3. Кэширование процессоров

public class CachingPaymentFactory {
    private final Map<PaymentType, PaymentProcessor> cache
        = new ConcurrentHashMap<>();
    private final PaymentProcessorFactory delegate;
    
    public PaymentProcessor create(PaymentType type) {
        return cache.computeIfAbsent(type, 
            t -> delegate.create(t)
        );
    }
}

Заключение

Factory паттерн — это:

  • Гибкость — легко добавлять новые типы
  • Инкапсуляция — логика создания в одном месте
  • Масштабируемость — система растёт без изменений core кода
  • Тестируемость — можно мокировать фабрику

Это один из самых используемых паттернов в enterprise разработке.

Как бы создал шаблон Factory | PrepBro