Strategy Pattern em Java: Implementando Algoritmos Intercambiáveis
Aprenda como implementar o Strategy Pattern em Java para criar algoritmos flexíveis e intercambiáveis. Descubra como este padrão comportamental pode melhorar a manutenibilidade do seu código.
Introdução
O Strategy Pattern (Padrão Estratégia) é um padrão de design comportamental que define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Este padrão permite que o algoritmo varie independentemente dos clientes que o utilizam, promovendo flexibilidade e reutilização de código.
Neste post, exploraremos o conceito do Strategy Pattern, suas vantagens e como implementá-lo em Java com exemplos práticos.
O que é o Strategy Pattern?
O Strategy Pattern resolve o problema de ter múltiplas maneiras de executar uma tarefa, permitindo que você escolha o algoritmo apropriado em tempo de execução. Em vez de usar condicionais complexas (if/else ou switch), o padrão encapsula cada algoritmo em classes separadas.
Principais componentes:
- Strategy (Estratégia): Interface que define o contrato comum para todos os algoritmos.
- ConcreteStrategy (Estratégia Concreta): Implementações específicas dos algoritmos.
- Context (Contexto): Classe que utiliza uma estratégia e pode alterá-la conforme necessário.
Quando usar o Strategy Pattern?
- Quando você tem múltiplas maneiras de realizar uma tarefa.
- Para evitar condicionais complexas que escolhem entre diferentes algoritmos.
- Quando você quer adicionar novos algoritmos sem modificar o código existente.
- Para promover o princípio Aberto/Fechado (Open/Closed Principle).
Exemplo Prático em Java
Vamos implementar um sistema de cálculo de desconto para um e-commerce, onde diferentes tipos de clientes recebem descontos distintos.
1. Criando a Interface Strategy
A interface define o contrato que todas as estratégias de desconto devem implementar:
public interface DiscountStrategy {
double calculateDiscount(double originalPrice);
String getDiscountDescription();
}
2. Implementando as Estratégias Concretas
Estratégia para Cliente Regular:
public class RegularCustomerDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double originalPrice) {
return originalPrice * 0.05; // 5% de desconto
}
@Override
public String getDiscountDescription() {
return "Desconto Cliente Regular (5%)";
}
}
Estratégia para Cliente Premium:
public class PremiumCustomerDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double originalPrice) {
return originalPrice * 0.15; // 15% de desconto
}
@Override
public String getDiscountDescription() {
return "Desconto Cliente Premium (15%)";
}
}
Estratégia para Cliente VIP:
public class VipCustomerDiscount implements DiscountStrategy {
@Override
public double calculateDiscount(double originalPrice) {
return originalPrice * 0.25; // 25% de desconto
}
@Override
public String getDiscountDescription() {
return "Desconto Cliente VIP (25%)";
}
}
3. Criando a Classe Context
A classe ShoppingCart atua como contexto, utilizando as estratégias de desconto:
public class ShoppingCart {
private DiscountStrategy discountStrategy;
public ShoppingCart(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public void setDiscountStrategy(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
public double calculateTotalPrice(double originalPrice) {
double discount = discountStrategy.calculateDiscount(originalPrice);
double finalPrice = originalPrice - discount;
System.out.println("Preço original: R$ " + String.format("%.2f", originalPrice));
System.out.println("Estratégia aplicada: " + discountStrategy.getDiscountDescription());
System.out.println("Desconto: R$ " + String.format("%.2f", discount));
System.out.println("Preço final: R$ " + String.format("%.2f", finalPrice));
System.out.println("---");
return finalPrice;
}
}
4. Criando o Cliente
Demonstramos como usar o Strategy Pattern em diferentes cenários:
public class StrategyPatternDemo {
public static void main(String[] args) {
double originalPrice = 100.0;
// Cliente Regular
ShoppingCart cart = new ShoppingCart(new RegularCustomerDiscount());
cart.calculateTotalPrice(originalPrice);
// Mudando para Cliente Premium
cart.setDiscountStrategy(new PremiumCustomerDiscount());
cart.calculateTotalPrice(originalPrice);
// Mudando para Cliente VIP
cart.setDiscountStrategy(new VipCustomerDiscount());
cart.calculateTotalPrice(originalPrice);
// Demonstrando flexibilidade - criando carrinho específico
ShoppingCart vipCart = new ShoppingCart(new VipCustomerDiscount());
vipCart.calculateTotalPrice(200.0);
}
}
Saída do Programa
Ao executar o código acima, você verá a seguinte saída:
Preço original: R$ 100,00
Estratégia aplicada: Desconto Cliente Regular (5%)
Desconto: R$ 5,00
Preço final: R$ 95,00
---
Preço original: R$ 100,00
Estratégia aplicada: Desconto Cliente Premium (15%)
Desconto: R$ 15,00
Preço final: R$ 85,00
---
Preço original: R$ 100,00
Estratégia aplicada: Desconto Cliente VIP (25%)
Desconto: R$ 25,00
Preço final: R$ 75,00
---
Preço original: R$ 200,00
Estratégia aplicada: Desconto Cliente VIP (25%)
Desconto: R$ 50,00
Preço final: R$ 150,00
---
Explicação do Código
Interface DiscountStrategy: Define o contrato comum para todos os algoritmos de desconto.
Estratégias Concretas: Cada classe implementa uma política de desconto específica (Regular, Premium, VIP).
Classe ShoppingCart: Atua como contexto, mantendo uma referência para a estratégia atual e delegando os cálculos para ela.
Flexibilidade: O algoritmo pode ser trocado em tempo de execução usando o método
setDiscountStrategy()
.
Vantagens e Desvantagens
Vantagens:
- Flexibilidade: Algoritmos podem ser trocados facilmente em tempo de execução.
- Extensibilidade: Novas estratégias podem ser adicionadas sem modificar código existente.
- Manutenibilidade: Cada algoritmo fica isolado em sua própria classe.
- Testabilidade: Cada estratégia pode ser testada independentemente.
- Princípio Aberto/Fechado: Aberto para extensão, fechado para modificação.
Desvantagens:
- Proliferação de Classes: Pode aumentar o número de classes no sistema.
- Complexidade Inicial: Para casos simples, pode ser excessivo.
- Conhecimento das Estratégias: O cliente precisa conhecer as diferentes estratégias disponíveis.
Variações do Strategy Pattern
Strategy com Enum:
public enum DiscountType {
REGULAR(0.05),
PREMIUM(0.15),
VIP(0.25);
private final double discountRate;
DiscountType(double discountRate) {
this.discountRate = discountRate;
}
public double calculateDiscount(double originalPrice) {
return originalPrice * discountRate;
}
}
Strategy com Expressões Lambda (Java 8+):
// Definindo estratégias como funções
Function<Double, Double> regularDiscount = price -> price * 0.05;
Function<Double, Double> premiumDiscount = price -> price * 0.15;
Function<Double, Double> vipDiscount = price -> price * 0.25;
// Uso das estratégias
double price = 100.0;
double discountRegular = regularDiscount.apply(price);
Quando evitar o Strategy Pattern?
- Para casos muito simples com apenas 2-3 opções, uma estrutura if/else pode ser suficiente.
- Quando os algoritmos raramente mudam ou são adicionados.
- Se a performance for crítica e o overhead de chamadas polimórficas for significativo.
Conclusão
O Strategy Pattern é uma solução elegante para cenários onde você precisa escolher entre diferentes algoritmos em tempo de execução. Ele promove código limpo, flexível e facilmente extensível, seguindo princípios sólidos de design orientado a objetos.
Este padrão é especialmente útil em Java, onde pode ser implementado de forma clara e eficiente. Se você trabalha com sistemas que precisam de flexibilidade algorítmica, o Strategy Pattern é uma ferramenta indispensável no seu arsenal de design patterns.
Gostou deste post? Continue acompanhando para mais conteúdos sobre padrões de design e desenvolvimento em Java!