No universo do desenvolvimento de software, a capacidade de criar sistemas flexíveis e de fácil manutenção é um desafio constante. Padrões de projeto surgem como soluções comprovadas para problemas recorrentes, oferecendo um vocabulário comum e diretrizes para a estruturação do código. Entre eles, o padrão de projeto Strategy se destaca como uma ferramenta para lidar com a variação de algoritmos e comportamentos em uma aplicação.
O padrão Strategy é uma forma de organizar o código para que ele possa usar diferentes comportamentos (ou algoritmos) de maneira flexível. Em vez de escrever toda a lógica diretamente em uma única classe, você separa cada comportamento em sua própria classe.
Dessa forma, a classe principal (que chamamos de contexto) pode trocar facilmente o comportamento que está usando, sem precisar ser alterada. Isso permite, por exemplo, mudar a forma de calcular um valor, aplicar descontos ou processar dados, apenas trocando o objeto que representa a estratégia.
Em resumo: o Strategy ajuda você a escolher e trocar comportamentos em tempo de execução, deixando o código mais organizado, reutilizável e fácil de manter.
Imagine uma aplicação que precisa realizar uma mesma tarefa de diversas maneiras, como calcular diferentes tipos de frete, aplicar diferentes taxas de juros ou processar pagamentos por métodos variados. Uma abordagem comum, mas problemática, seria utilizar estruturas condicionais (como if-else ou switch-case ) para selecionar o algoritmo apropriado com base em algum critério. Essa abordagem leva a um código “inchado”, difícil de manter e estender. Cada nova variação do algoritmo exigiria modificações na classe principal
Para ilustrar o padrão Strategy, vamos considerar um cenário simples onde precisamos modelar um sistema de cálculo de frete com diferentes estratégias. Usaremos o padrão Strategy para encapsular essas operações e permitir que sejam trocadas dinamicamente.
Primeiro, definimos a interface “IFreteStrategy” , que declara o método “CalcularFrete”. Todas as estratégias concretas deverão implementar este método.
public interface IFreteStrategy
{
decimal CalcularFrete(decimal peso);
}
Em seguida, vamos criar as implementações concretas da interface “IFreteStrategy”. Cada uma delas implementa uma lógica diferente para o método “CalcularFrete”.
public class FreteNormal : IFreteStrategy
{
public decimal CalcularFrete(decimal peso) => peso * 1.0m;
}
public class FreteExpresso : IFreteStrategy
{
public decimal CalcularFrete(decimal peso) => peso * 2.0m + 10.0m;
}
public class FreteRetiradaLoja : IFreteStrategy
{
public decimal CalcularFrete(decimal peso) => 0.0m;
}
Cada classe implementa a interface “IFreteStrategy” com uma lógica própria:
FreteNormal: calcula o frete de forma linear com base no peso.
FreteExpresso: aplica um multiplicador maior e uma taxa fixa adicional.
FreteRetiradaLoja: define um frete com valor fixo zero (para casos de retirada na loja).
Esta abordagem encapsula cada algoritmo em sua própria classe, permitindo a intercambialidade conforme a necessidade.
public class CalculadoraFrete
{
private IFreteStrategy _strategy;
public CalculadoraFrete(IFreteStrategy strategy)
{
_strategy = strategy;
}
public void DefinirEstrategia(IFreteStrategy strategy)
{
_strategy = strategy;
}
public decimal Calcular(decimal peso)
{
return _strategy.CalcularFrete(peso);
}
}
A classe “CalculadoraFrete” atua como o contexto. Ela não se preocupa com os detalhes de cada algoritmo, apenas delega o cálculo para a estratégia atual. O método “DefinirEstrategia” possibilita trocar o comportamento em tempo de execução, mantendo o código aberto para novas estratégias sem modificar sua estrutura interna – um claro exemplo do princípio do aberto/fechado.
public class Program
{
public static async Task Main()
{
decimal peso = 5.0m;
var calculadora = new CalculadoraFrete(new FreteNormal());
var valorFreteNormal = calculadora.Calcular(peso);
Console.WriteLine($"Frete Normal: R$ {valorFreteNormal}");
calculadora.DefinirEstrategia(new FreteExpresso());
var valorFreteExpresso = calculadora.Calcular(peso);
Console.WriteLine($"Frete Expresso: R$ {calculadora.Calcular(peso)}");
calculadora.DefinirEstrategia(new FreteRetiradaLoja());
var valorFreteRetiradaLoja = calculadora.Calcular(peso);
Console.WriteLine($"Retirada na Loja: R$ {calculadora.Calcular(peso)}");
}
}
Na primeira parte do código, o contexto “CalculadoraFrete” é instanciado com a estratégia “FreteNormal”. Ao chamar o método “Calcular”, o frete é computado com base na lógica definida em “FreteNormal”. O resultado deste cálculo será “Frete Normal: R$ 5,00”.
Na segunda parte, realizamos o mesmo procedimento, mas trocamos a estratégia para “FreteExpresso”. Neste caso o resultado será diferente: “Frete Expresso: R$ 20,00”.
Na terceira parte, alteramos novamente a estratégia para “FreteRetiradaLoja”, por conta disso, o valor do frete será de R$ 0, pois o cliente irá realizar a retirada na loja.
Essa dinâmica demonstra como a estratégia permite alterar o comportamento em tempo de execução sem modificar o cliente ou o contexto.
Flexibilidade: permite alterar ou estender comportamentos sem impactar o restante do sistema.
Facilidade de manutenção: cada estratégia é isolada e pode ser testada e modificada independentemente.
Reutilização: estratégias podem ser reutilizadas em diferentes contextos ou aplicações.
Aderência ao princípio do aberto/fechado: novas estratégias podem ser adicionadas sem modificar o código existente, o que torna o sistema mais robusto a mudanças futuras.
Aumento no número de classes: cada comportamento exige uma nova classe, o que pode tornar a estrutura do projeto mais complexa.
Complexidade desnecessária: em cenários simples, o uso do padrão pode ser excessivo quando uma simples função ou delegate já resolveria o problema.
Gerenciamento de estado: se os algoritmos dependem fortemente do estado do contexto, pode ser necessário passar muitas informações como parâmetros, o que pode complicar a implementação.
Se você é Desenvolvedor .NET Júnior e quer acelerar sua carreira até nível Pleno com salário de R$7k+, ou mesmo busca a primeira vaga, conheça a Mentoria .NET Start: Clique aqui
Se é Desenvolvedor .NET Pleno ou Sênior e quer virar referência técnica em sua equipe e mercado, com salário de R$10k+, conheça a Mentoria .NET Expert: Clique aqui
O padrão Strategy facilita a variação de comportamentos em um sistema, promovendo modularidade e escalabilidade. Ele separa algoritmos do contexto em que são usados, tornando o código mais flexível e preparado para mudanças futuras. Além de melhorar a manutenção e testabilidade, o Strategy evita estruturas condicionais complexas. Embora aumente o número de classes, os benefícios em organização e reutilização fazem dele uma abordagem eficiente para sistemas dinâmicos e evolutivos.