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.