Você já deve ter se deparou com um código parecido com isso:
@Service
public class PaymentService {
public void pay(Payment payment) {
PaymentMethod method = payment.getMethod();
double amount = payment.getAmount();
if (method == PaymentMethod.CREDIT_CARD) {
System.out.println("Processing Credit Card payment of amount " + amount);
} else if (method == PaymentMethod.DEBIT_CARD) {
System.out.println("Processing Debit Card payment of amount " + amount);
} else if (method == PaymentMethod.PAYPAL) {
System.out.println("Processing PayPal payment of amount " + amount);
} else {
throw new IllegalArgumentException("Invalid payment type");
}
}
}
Mas qual o problema desse código?
O problema é que se você precisar adicionar um novo método de pagamento, você terá que modificar o método process, o que implicará em alteração dos testes e aumento da complexidade do método.
Para resolver esse problema, temos um design pattern chamado Strategy e o Spring Framework oferece uma extensão chamada Spring-Plugin que torna a implementação do desse padrão de forma simples e direta.
O que é o padrão Strategy?
O Strategy (pode ser chamado de policy) é um design pattern comportamental que permite permite alterar dinamicamente o comportamento de um objeto em tempo de execução, encapsulando-o em diferentes estratégias.
Esse padrão é particularmente útil quando você tem uma classe com um comportamento que pode variar de acordo com algumas condições e deseja evitar o acoplamento excessivo.
Quais os benefícios desse padrão?
Separar Responsabilidades: Permite que você separe a lógica de negócios específica das classes que a utilizam. Isso torna seu código mais modular e mais fácil de ler e manter.
Reutilização: Algoritmos encapsulados podem ser reutilizados em diferentes partes do sistema ou até mesmo em outros sistemas, evitando a duplicação de código.
Flexibilidade: Ajuda a reduzir as estruturas condicionais complexas que você poderia ter ao implementar várias versões de um algoritmo em uma única classe. Em vez disso, você pode definir cada versão do algoritmo em sua própria classe Strategy.
Testabilidade: Como cada estratégia é implementada como uma classe separada, ela pode ser testada de forma isolada. Isso facilita a escrita de testes unitários e garante que cada parte do seu sistema funcione como esperado.
Escalabilidade: Ao adicionar novos comportamentos ou algoritmos, você não precisa modificar o código existente; apenas adicione uma nova implementação da estratégia. Isso minimiza o risco de introduzir erros em partes do sistema que já funcionam.
O que é Spring-Plugin?
Spring-Plugin é uma biblioteca que fornece um mecanismo simples para expor e procurar plugins dentro do contexto da aplicação Spring. Permite a descoberta dinâmica de beans que se conformam a certos critérios sem a necessidade de declarar todos eles explicitamente.
Padrão Strategy usando Spring-Plugin:
Vamos implementar a seguinte estrutura:

Adicione a dependência do Spring-Plugin: O primeiro passo é adicionar a dependência do Spring-Plugin ao seu projeto. Isso pode ser feito usando Maven.
<dependency>
<groupId>org.springframework.plugin</groupId>
<artifactId>spring-plugin-core</artifactId>
<version>3.0.0</version>
</dependency>
Defina a Interface de Estratégia: Defina a interface Strategy que servirá como contrato para todas as implementações, essa interface deve entender a interface Plugin.
import org.springframework.plugin.core.Plugin;
public interface PaymentStrategy extends Plugin<PaymentMethod> {
void process(Payment payment);
}
Implemente as Estratégias: Crie classes concretas implementando a interface de estratégia. Cada implementação de pagamento deverá implementar o método supports
para indicar se suporta ou não um método de pagamento específico:
@Component
public class PayPalPaymentStrategy implements PaymentStrategy {
@Override
public void process(Payment payment) {
System.out.println("Processing PayPal payment of amount " + payment.getAmount());
}
@Override
public boolean supports(PaymentMethod delimiter) {
return PaymentMethod.PAYPAL.equals(delimiter); // Suporta pagamentos com PayPal
}
}
@Component
public class CreditCardPaymentStrategy implements PaymentStrategy {
@Override
public void process(Payment payment) {
System.out.println("Processing Credit Card payment of amount " + payment.getAmount());
}
@Override
public boolean supports(PaymentMethod delimiter) {
return PaymentMethod.CREDIT_CARD.equals(delimiter); // Suporta pagamentos com cartão de crédito
}
}
Observação: Como você anotou as implementações com @Component
, o Spring detectará e gerenciará automaticamente.
Habilite os repositórios de plugins no contexto Spring: Com a anotação @EnablePluginRegistries
, o Spring-Plugin cuidará automaticamente da descoberta dos beans de plugins no contexto da aplicação
@Configuration
@EnablePluginRegistries(PaymentStrategy.class) // sua interface Strategy
public class PluginConfiguration {
}
Use PluginRegistry: Injecte o PluginRegistry
na classe de serviço ou onde você precisará usar as estratégias
@Autowired
private final PluginRegistry<PaymentStrategy, PaymentMethod> pluginRegistry;
Selecionando a Estratégia Correta: Agora que todas as estratégias estão registradas no PluginRegistry
, você pode buscar e usar a estratégia correta com base nas condições em tempo de execução.
public void pay(Payment payment) {
// Retorna o primeiro plugin que suporta o método de pagamento passado
PaymentStrategy paymentStrategy = pluginRegistry.getPluginFor(payment.getMethod())
.orElseThrow(() -> new IllegalArgumentException("Invalid payment strategy"));
// executa o método process dinamicamente
paymentStrategy.process(payment);
}
Estrutura do projeto: veja como ficou a estrutura do nosso projeto?

Testando: Ao executar a classe de teste
@SpringBootTest
public class PaymentServiceTest {
@Autowired
PaymentService paymentService;
@Test
void testStrategies() {
paymentService.pay(new Payment(100, PaymentMethod.CREDIT_CARD));
paymentService.pay(new Payment(100, PaymentMethod.PAYPAL));
}
}
Devemos ter o seguinte resultado:

Conclusão
A biblioteca Spring-Plugin torna a implementação do padrão estratégia uma tarefa sem esforço. Não apenas oferece uma separação clara de preocupações e facilidade de manutenção, como também permite uma seleção flexível e dinâmica de estratégias em tempo de execução.