DEV Community

Cover image for Strategy Design Pattern
João Marcos Cerqueira
João Marcos Cerqueira

Posted on • Edited on

Strategy Design Pattern

Tá, antes de tudo, você sabe o que são design patterns? Design patterns são soluções reutilizáveis e já bem estabelecidas para problemas comuns que nós, programadores, enfrentamos no dia a dia.

Surgidos através do trabalho de diversos programadores experientes, se tornaram um consenso na resolução de problemas, a fim de tornar o seu código mais limpo, elegante e de fácil manutenção. Talvez você entenda melhor conforme vá realizando essa leitura.

O problema a se resolver

Já que estamos falando de uma solução, estamos resolvendo algum problema, certo?

Para demonstrar o problema, vou criar o seguinte contexto:

Vamos considerar um sistema simples para calcular descontos em uma loja online com base em diferentes tipos de cupons existentes: EasySave ou ClearCut, onde cada um tem sua estratégia de desconto → o EasySave fornece 20% de desconto e o ClearCut fornece apenas 10%.

O codigo inicial está da seguinte forma:

public class Order
{
    public decimal Value { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
public class DiscountCalculator
{
    public void Calculate(Order order, string coupon) 
    {        
        if( "EasySave".Equals(coupon))
        {   
            decimal value = order.Value * 0.8;
            Console.WriteLine(value);
        } 
        else if( "ClearCut".Equals(coupon)) 
        {    
            decimal value = order.Value * 0.9;
            Console.WriteLine(value);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Existem alguns problemas nessa implementação:

Se formos analisar, para cada novo desconto criado, será necessário criar um novo if, podendo se tornar um método muito grande e difícil de se ler e consequentemente, de se manter. Outra questão, é que essa classe está pouco coesa, as regras de negócio e cálculos estão espalhadas por ela.

Algumas melhorias

Para resolver o problema da coesão, podemos separar a responsabilidade do cálculo dos descontos para classes específicas de cada um, dessa forma:

public class EasySave
{
    public decimal Calculate(Order order)
    {
        return order.Value * 0.8;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class ClearCut
{
    public decimal Calculate(Order order)
    {
        return order.Value * 0.9;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class DiscountCalculator
{
    public void Calculate(Order order, string coupon)
    {
        if ("EasySave".Equals(coupon))
        {
            decimal value = new EasySave().Calculate(order);
            Console.WriteLine(value);
        }
        else if ("ClearCut".Equals(coupon))
        {
            decimal value = new ClearCut().Calculate(order);
            Console.WriteLine(value);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Pronto, resolvemos o problema de coesão agora que cada classe de desconto é responsável por suas regras. No entanto, ainda temos o problema da quantidade de if que esse bloco de código pode ter para cada desconto novo adicionado.

Para remover as condicionais desse método, temos que remover o parâmetro coupoun que informa, através de uma string, o desconto que será utilizado, concorda?

Tá, mas como saberemos qual desconto será aplicado? Uma solução, seria criar um método separado para cada desconto, dessa maneira:

public class DiscountCalculator
{
    public void CalculateEasySave(Order order)
    {
        decimal value = new EasySave().Calculate(order);
        Console.WriteLine(value);
    }
    public void CalculateClearCut(Order order)
    {
        decimal value = new ClearCut().Calculate(order);
        Console.WriteLine(value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora ficou melhor, mas ainda não resolve o problema, já que agora, ao invés de ter um novo if, ainda teremos que criar um novo método para cada imposto. Então, como resolver?

A solução final

Como você provavelmente já percebeu, os dois métodos que criamos na classe DiscountCalculator implementam uma estratégia específica para o seu desconto, mas no final das contas, não será sempre sobre um desconto e, nesse caso o cálculo em cima dele?

Isso significa que podemos criar uma abstração de desconto, e definir um método genérico que deva calcular o valor do pedido com o desconto!

Faremos essa abstração da seguinte maneira, utilizando uma interface:

public interface Discount
{
    public decimal Calculate(Order order);
}
Enter fullscreen mode Exit fullscreen mode

Com a interface criada, devemos agora fazer com que as classes EasySave e ClearCut herdem da interface Discount:

public class EasySave : Discount
{
    public decimal Calculate(Order order)
    {
        return order.Value * 0.8;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class ClearCut : Discount
{
    public decimal Calculate(Order order)
    {
        return order.Value * 0.9;
    }
}
Enter fullscreen mode Exit fullscreen mode

Para finalizar, na classe DiscountCalculator, ao invés de utilizar um método para cada desconto existente na nossa aplicação, utilizaremos só um método que receberá como parâmetro, além do pedido, o desconto a ser calculado, assim:

public class DiscountCalculator
{
    public void Calculate(Order order, Discount discount)
    {
        decimal value = discount.Calculate(order);
        Console.WriteLine(value);
    }
}
Enter fullscreen mode Exit fullscreen mode

Pronto! Agora, a criação de novos tipos de desconto não implica mais numa mudança da classe DiscountCalculator, apenas na criação de uma nova classe que implemente a interface Discount . Com isso, temos um código mais limpo e mais fácil de se realizar manutenção.

Guia do Refactoring Guru sobre o padrão Strategy

Aaah, lembre-se sempre que um design pattern não é sobre seguir a implementação à risca, é sobre seguir a ideia.

Top comments (0)