Neste post, vou mergulhar em um design pattern que resolveu um desafio prático de arquitetura de software: a necessidade de aplicar um padrão de execução específico para processar uma requisição, dependendo do tipo de pagamento retornado em um response. Para cada modalidade de pagamento, como cartão de crédito, PayPal, etc. Uma ação distinta precisava ser executada.
A solução que se destacou foi a aplicação do Padrão de Projeto Strategy. Ele se mostrou ideal porque permite isolar cada lógica de processamento em sua própria classe (sua "estratégia"), tornando o código mais limpo, claro para manutenção e, crucialmente, altamente escalável para o futuro, caso novos métodos de pagamento sejam adicionados.
Lembrete: o código que apresento é uma simplificação do ambiente real, mas o foco está na essência da solução arquitetônica.
1. Definindo a Interface de Processamento
O primeiro passo foi estabelecer o contrato comum para todas as estratégias de pagamento. Criei a interface PaymentProcessor, que define o método que será implementado por cada classe de processamento especializada.
public interface PaymentProcessor {
boolean process(final PaymentResponse request);
}
2. O Serviço que Gerencia as Estratégias
Em seguida, criei a classe PaymentService, o coração da nossa solução. Sua responsabilidade é mapear e gerenciar todas as implementações da interface PaymentProcessor. Isso nos permite identificar qual classe é responsável por cada tipo de execução. No exemplo, iniciei com as estratégias Paypal e Credit Card. Para garantir a qualidade e um controle rigoroso dos tipos suportados, utilizei um Enum chamado PaymentType, facilitando a adição ou remoção de novas modalidades, como um futuro Debit Card.
@Service
public class PaymentService {
private Map<PaymentType, PaymentProcessor> processors;
public PaymentService(@Qualifier("paypal") PaymentProcessor paypal,
@Qualifier("credit_card") PaymentProcessor credit) {
processors = Map.of(PaymentType.PAYPAL, paypal, PaymentType.CREDIT_CARD, credit);
}
public boolean process(String type, PaymentResponse request) {
PaymentType paymentType = PaymentType.valueOf(type.toUpperCase());
PaymentProcessor processor = processors.get(paymentType);
if (Objects.isNull(processor)) {
throw new IllegalArgumentException("Type not supported: " + type);
}
return processor.process(request);
}
}
public enum PaymentType {
PAYPAL, CREDIT_CARD, DEBIT_CARD
}
3. Implementando as Estratégias de Pagamento
Agora, criamos as classes PayPalProcessor e CreditCardProcessor. Ambas implementam a interface PaymentProcessor. Utilizando o framework Spring Boot, estas classes são anotadas para que, ao iniciar a aplicação, o framework possa identificá-las e gerenciar seus beans corretamente, injetando-os no PaymentService. Dessa forma, o PaymentService terá todas as estratégias mapeadas e prontas para uso.
@Component
@Qualifier("paypal")
public class PayPalProcessor implements PaymentProcessor{
@Override
public boolean process(PaymentResponse request) {
System.out.println("✅ PayPal processed!");
return true;
}
}
@Component
@Qualifier("credit_card")
public class CreditCardProcessor implements PaymentProcessor{
@Override
public boolean process(PaymentResponse request) {
System.out.println("✅ credit card processed!");
return true;
}
}
4. A Execução e o Fim dos IFs
Chegamos ao ponto crucial, a execução. Recebemos o PaymentResponse (que simula o retorno de outras APIs), e seu campo paymentType informa qual tipo de pagamento estamos lidando. O PaymentService entra em ação, processando a informação diretamente. A grande vitória aqui é a eliminação da necessidade de blocos condicionais (IF/ELSE ou SWITCH/CASE) para verificar o tipo de pagamento.
Graças à gestão de beans do Spring Boot, no momento da requisição, o PaymentService simplesmente solicita a estratégia (get) para o paymentType informado e executa o método correto. Essa abordagem não apenas melhora a manutenibilidade, mas também busca garantir o safety mode, prevenindo erros comuns em Java, como o temido NullPointerException.
Top comments (0)