Padrão de projeto Strategy em Java: Como encapsular algoritmos e torná-los intercambiáveis

Por Gaspar Barancelli Junior em 16 de setembro de 2023

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 calcularPagamento() 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.

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 CalculoPagamentoCartao e passá-lo para o construtor da classe 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 pagar 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).

Cada enum tem sua própria implementação do método pagar, que contém a lógica específica para cada forma de pagamento.

Para utilizar o padrão, basta passar o enum TipoDePagamento desejado para a classe que realiza o pagamento, que irá chamar o método pagar correspondente.

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 processar que recebe o valor do pagamento e a estratégia de pagamento a ser utilizada. Ao chamar o método pagar no enum passado como parâmetro, a lógica de pagamento correspondente é executada.

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.

// Compartilhe esse Post

💫
🔥 NOVO APP

Domine o Inglês em 30 dias!

Inteligência Artificial + Repetição Espaçada • Método cientificamente comprovado

✅ Grátis para começar 🚀 Resultados rápidos
×