Как бы создал шаблон Factory
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Создание шаблона 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 разработке.