DEV Community

Marcos Belorio
Marcos Belorio

Posted on

Design Pattern - Strategy

Vamos falar hoje de um pattern bem interessante quando trabalhamos com programação orientada a objetos, que pode ser aplicado em muitos casos do nosso dia a dia. Durante esse artigo vamos conhecer a definição do pattern, ver um exemplo de código que frequentemente nos deparamos no dia a dia e como podemos melhorá-lo aplicando o pattern.

Os exemplos desse artigo serão em C# porém o pattern é facilmente aplicado em outras linguagens.

Definição

Segundo Erich Gamma em seu livro sobre Design Patterns, a definição para o pattern strategy é:

Definir uma família de algoritmos, encapsular cada uma delas e torná-las intercambiáveis. Strategy permite que o algoritmo varie independentemente dos clientes que o utilizam.

Implementando um exemplo para pagamentos

No nosso exemplo iremos criar um código para efetuar pagamento, seja ele por cartão de crédito, débito e outras modalidades. Para isso então vamos criar suas interfaces.

public interface IPagamentoCartaoCreditoService
{
    string EfetuarPagamento(decimal valor);
}

public interface IPagamentoCartaoDebitoService
{
    string EfetuarPagamento(decimal valor);
}
Enter fullscreen mode Exit fullscreen mode

Criamos uma interface para cada forma de pagamento que possui declarado o método EfetuarPagamento.

Vamos agora implementar as classes concretas das interfaces.

public class PagamentoCartaoCreditoService 
    : IPagamentoCartaoCreditoService
{
    public string EfetuarPagamento(decimal valor) 
        => $"Pagamento efetuado com o cartão de crédito no valor de R${valor}";
}

public class PagamentoCartaoDebitoService 
    : IPagamentoCartaoDebitoService
{
    public string EfetuarPagamento(decimal valor) 
        => $"Pagamento efetuado com o cartão de débito no valor de R${valor}";
}
Enter fullscreen mode Exit fullscreen mode

E por fim vamos implementar nossa regra de pagamento.

public class PagamentoService
{
    private readonly IPagamentoCartaoCreditoService _cartaoCreditoService;
    private readonly IPagamentoCartaoDebitoService _cartaoDebitoService;

    public PagamentoService(IPagamentoCartaoCreditoService cartaoCreditoService,
        IPagamentoCartaoDebitoService cartaoDebitoService)
    {
        _cartaoCreditoService = cartaoCreditoService;
        _cartaoDebitoService = cartaoDebitoService;
    }

    public string EfetuarPagamento(FormaPagamentoEnum formaPagamento, decimal valor)
    {
        if (formaPagamento == FormaPagamentoEnum.CartaoCredito)
            return _cartaoCreditoService.EfetuarPagamento(valor);
        else if (formaPagamento == FormaPagamentoEnum.CartaoDebito)
            return _cartaoDebitoService.EfetuarPagamento(valor);
        else
            throw new Exception("Forma de pagamento não encontrada.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Implementamos um método para efetuar o pagamento e dependendo do valor passado pelo enum ele efetua o pagamento por cartão de crédito ou por cartão de débito. Acredito que você já tenha visto um código similar a esse, note que não tem nada de errado nesse código, mas agora vamos ver o que precisamos para implementar uma nova forma de pagamento.

  1. Implementar a nova forma de pagamento (interface e classe concreta)
  2. Modificar o construtor da classe PagamentoService passando a interface da nova forma de pagamento.
  3. Criar uma nova condição if para poder ser utilizado essa nova forma de pagamento.

Se atualizarmos esse código com dez novas formas de pagamento (o que não é difícil hoje em dia com tantas opções de pagamento hehehe) teremos um código cada vez maior, complexo e aberto constantemente a modificações (o que vai contra a letra 'O' dos princípios do SOLID).

Então vamos aplicar algumas melhorias nesse código com o nosso pattern strategy.

Implementando o pattern strategy

Vamos começar criando apenas uma interface de pagamento de forma genérica.

public interface IPagamentoService
{
    FormaPagamentoEnum formaPagamento { get; }
    string EfetuarPagamento(decimal valor);
}
Enter fullscreen mode Exit fullscreen mode

Perceba que agora definimos o enum dentro da interface também. As classes concretas são implementadas à partir desta interface.

public class PagamentoCartaoCreditoService 
    : IPagamentoService
{
    public FormaPagamentoEnum formaPagamento 
        => FormaPagamentoEnum.CartaoCredito;

    public string EfetuarPagamento(decimal valor) 
        => $"Pagamento efetuado com o cartão de crédito no valor de R${valor}";
}

public class PagamentoCartaoDebitoService 
    : IPagamentoService
{
    public FormaPagamentoEnum formaPagamento 
        => FormaPagamentoEnum.CartaoDebito;

    public string EfetuarPagamento(decimal valor) 
        => $"Pagamento efetuado com o cartão de débito no valor de R${valor}";
}
Enter fullscreen mode Exit fullscreen mode

E agora vem a parte mais interessante que é a implementação do pattern.

public class PagamentoStrategyService 
    : IPagamentoStrategyService
{
    private readonly IEnumerable<IPagamentoService> _services;

    public PagamentoStrategyService(IEnumerable<IPagamentoService> services)
    {
        _services = services;
    }

    public string EfetuarPagamento(FormaPagamentoEnum formaPagamento, decimal valor)
    {
        return _services.FirstOrDefault(x => x.formaPagamento == formaPagamento)?
            .EfetuarPagamento(valor) ?? 
            throw new ArgumentNullException(nameof(formaPagamento));
    }
}
Enter fullscreen mode Exit fullscreen mode

Como temos duas classes que implementam a mesma interface IPagamentoService, recebemos no construtor um IEnumerable contendo as duas implementações. No método de efetuar pagamento criamos uma expressão lambda filtrando qual implementação da interface IPagamentoService iremos utilizar à partir do valor do enum.

Muito simples não? Você não vai mais precisar de vários if else, a classe estará pronta para receber quantas formas de pagamento forem necessárias sem precisar modificá-la. Toda vez que precisarmos implementar uma nova forma de pagamento o único passo será:

  1. Implementar a nova forma de pagamento (classe concreta utilizando a interface IPagamentoService)

Projeto de exemplo

Caso tenha ficado ainda alguma dúvida no código, você pode ver o exemplo que eu criei nesse repositório do Github.

Se gostou utilize os botões do lado esquerdo para que eu possa saber. Caso tenha alguma dúvida, sugestão ou crítica, deixe um comentário logo abaixo.

Muito obrigado!

Referências:
What You Need To Know About The Helpful Strategy Pattern
WestDiscGolf/StrategyPatternExample

Discussion (0)