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; }
}
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);
}
}
}
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;
}
}
public class ClearCut
{
public decimal Calculate(Order order)
{
return order.Value * 0.9;
}
}
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);
}
}
}
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);
}
}
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);
}
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;
}
}
public class ClearCut : Discount
{
public decimal Calculate(Order order)
{
return order.Value * 0.9;
}
}
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);
}
}
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)