Padrão de projeto Strategy em Java: Como encapsular algoritmos e torná-los intercambiáveis
O Padrão Strategy é um dos padrões de projeto mais comuns usados em desenvolvimento de software orientado a objetos. Ele permite que você defina uma família de algoritmos, encapsule cada um deles e os torne intercambiáveis. Isso permite que o algoritmo seja alterado independentemente dos clientes que o usam. Neste artigo, discutiremos o Padrão Strategy em detalhes, incluindo sua definição, quando utilizá-lo e exemplos de código em Java.
O padrão é composto por três partes principais:
-
Uma interface comum para todos os algoritmos.
-
Classes concretas ou enums que implementam a interface comum e fornecem a implementação específica de cada algoritmo.
-
Uma classe de contexto que utiliza um objeto de estratégia para executar um algoritmo específico.
Quando utilizar o Padrão Strategy
O Padrão Strategy é uma ótima opção quando você precisa alterar o comportamento de um objeto dinamicamente ou quando precisa alterar o comportamento de um objeto em tempo de execução. Além disso, o padrão é útil quando você tem um objeto que precisa de muitos comportamentos diferentes, mas não quer ter muitas classes para cada comportamento.
Primeiro: Exemplo de Código em Java
Vamos agora ver um exemplo simples de como usar o Padrão Strategy em Java.
Digamos que temos uma classe chamada Pagamento, que tem um método
que retorna o valor do pagamento. No entanto, dependendo do tipo de pagamento, o valor do pagamento é calculado de maneira diferente. Podemos usar o Padrão Strategy para definir diferentes algoritmos de cálculo de pagamento e encapsulá-los em classes separadas.calcularPagamento()
Primeiro, criamos uma interface comum para todos os algoritmos de cálculo de pagamento:
public interface CalculoPagamento {
public double calcular(double valorCompra);
}
Em seguida, criamos as classes concretas que implementam a interface comum e fornecem a implementação específica de cada algoritmo:
public class CalculoPagamentoCartao implements CalculoPagamento {
public double calcular(double valorCompra) {
return valorCompra * 1.05; // taxa de 5% para pagamentos com cartão
}
}
public class CalculoPagamentoBoleto implements CalculoPagamento {
public double calcular(double valorCompra) {
return valorCompra * 0.95; // desconto de 5% para pagamentos com boleto
}
}
Por fim, criamos uma classe de contexto que utiliza um objeto de estratégia para executar um algoritmo específico. No exemplo abaixo, a classe Pagamento usa uma instância de CalculoPagamento para calcular o valor do pagamento:
public class Pagamento {
private CalculoPagamento calculoPagamento;
public Pagamento(CalculoPagamento calculoPagamento) {
this.calculoPagamento = calculoPagamento;
}
public double calcularPagamento(double valorCompra) {
return calculoPagamento.calcular(valorCompra);
}
}
Dessa forma, podemos utilizar o padrão Strategy para poder calcular o pagamento envolvendo diferentes tipos de acerto, tudo de forma transparante pelo sistema, sem precisar modificar o código que faz o pagamento. Para calcular um pagamento com cartão, por exemplo, bastaria criar um objeto do tipo
e passá-lo para o construtor da classe CalculoPagamentoCartao
:Pagamento
CalculoPagamentoCartao pagamentoPorCartao = new CalculoPagamentoCartao();
Pagamento pagamento = new Pagamento(pagamentoPorCartao);
pagamento.calcularPagamento(100.0);
Da mesma forma, podemos calcular o valor de pagamento de um boleto:
CalculoPagamentoBoleto pagamentoPorBoleto = new CalculoPagamentoBoleto();
Pagamento pagamento = new Pagamento(pagamentoPorBoleto);
pagamento.calcularPagamento(100.0);
Segundo: Exemplo de Código em Java
// Interface que define o contrato para os algoritmos
public interface Algoritmo {
int executar(int a, int b);
}
// Implementação de um algoritmo específico
public class AlgoritmoSoma implements Algoritmo {
@Override
public int executar(int a, int b) {
return a + b;
}
}
// Implementação de outro algoritmo específico
public class AlgoritmoSubtracao implements Algoritmo {
@Override
public int executar(int a, int b) {
return a - b;
}
}
// Classe que utiliza o padrão Strategy para escolher o algoritmo em tempo de execução
public class Calculadora {
private Algoritmo algoritmo;
public Calculadora(Algoritmo algoritmo) {
this.algoritmo = algoritmo;
}
public int executarOperacao(int a, int b) {
return algoritmo.executar(a, b);
}
public void setAlgoritmo(Algoritmo algoritmo) {
this.algoritmo = algoritmo;
}
}
// Exemplo de uso da calculadora com o algoritmo de soma
public class ExemploCalculadora {
public static void main(String[] args) {
Calculadora calculadora = new Calculadora(new AlgoritmoSoma());
int resultado = calculadora.executarOperacao(10, 5);
System.out.println("Resultado: " + resultado); // Saída: Resultado: 15
}
}
Nesse exemplo, a interface Algoritmo define o contrato para os algoritmos que serão implementados. Em seguida, são criadas as implementações AlgoritmoSoma e AlgoritmoSubtracao.
A classe Calculadora utiliza o padrão Strategy para escolher o algoritmo a ser executado em tempo de execução, recebendo uma instância de Algoritmo no construtor. O método executarOperacao executa o algoritmo definido atualmente em algoritmo.
No exemplo ExemploCalculadora, é criada uma instância de Calculadora com o algoritmo de soma e é chamado o método executarOperacao com os valores 10 e 5, retornando 15.
Terceiro: Exemplo de Código em Java
public interface EstrategiaDePagamento {
void pagar(double valor);
}
public enum TipoDePagamento implements EstrategiaDePagamento {
CARTAO_DE_CREDITO {
@Override
public void pagar(double valor) {
// Implementação para pagamento com cartão de crédito
}
},
CARTAO_DE_DEBITO {
@Override
public void pagar(double valor) {
// Implementação para pagamento com cartão de débito
}
},
PAYPAL {
@Override
public void pagar(double valor) {
// Implementação para pagamento com PayPal
}
},
BOLETO {
@Override
public void pagar(double valor) {
// Implementação para pagamento com Boleto
}
}
}
Neste exemplo, criamos uma interface chamada EstrategiaDePagamento, que define o método
para realizar o pagamento. Em seguida, criamos um enum chamado TipoDePagamento, que implementa a interface EstrategiaDePagamento e define as estratégias de pagamento disponíveis (cartão de crédito, cartão de débito, PayPal e Boleto).pagar
Cada enum tem sua própria implementação do método
, que contém a lógica específica para cada forma de pagamento.pagar
Para utilizar o padrão, basta passar o enum TipoDePagamento desejado para a classe que realiza o pagamento, que irá chamar o método
correspondente.pagar
public class ProcessarPagamento {
public void processar(double valor, EstrategiaDePagamento estrategiaDePagamento) {
estrategiaDePagamento.pagar(valor);
}
}
public class Main {
public static void main(String[] args) {
ProcessarPagamento processarPagamento = new ProcessarPagamento();
// Pagamento com cartão de crédito
processarPagamento.processar(100.00, TipoDePagamento.CARTAO_DE_CREDITO);
// Pagamento com cartão de débito
processarPagamento.processar(220.00, TipoDePagamento.CARTAO_DE_DEBITO);
// Pagamento com PayPal
processarPagamento.processar(50.00, TipoDePagamento.PAYPAL);
// Pagamento com Boleto
processarPagamento.processar(75.00, TipoDePagamento.BOLETO);
}
}
No exemplo acima, criamos uma classe chamada ProcessarPagamento, que possui um método chamado
que recebe o valor do pagamento e a estratégia de pagamento a ser utilizada. Ao chamar o método processar
no enum passado como parâmetro, a lógica de pagamento correspondente é executada.pagar
Conclusão
Em resumo, o padrão Strategy é uma forma de separar a lógica de um algoritmo das diferentes implementações possíveis. Isso permite que diferentes variações de um algoritmo sejam utilizadas de forma transparente pelo sistema, sem precisar modificar o código que o utiliza. Além disso, o padrão Strategy promove uma maior flexibilidade e extensibilidade do código, uma vez que novas estratégias podem ser adicionadas ao sistema sem que seja necessário modificar o código existente. Com isso, é possível alterar o comportamento do sistema de maneira mais fácil e eficiente, facilitando a manutenção e evolução do software.