Executando verificação de segurança...
5

Como acabar com os IFS ANINHADOS​ do código? 😱

Um dos maiores desafios na jornada de um desenvolvedor é manter o código legível e desacoplado. Respeitar os princípios e adotar as melhores práticas de desenvolvimento pode ser um grande desafio durante o cotidiano de um profissional, onde existem prazos e diversos fatores que influenciam o mesmo.

Porém, existem técnicas que podem ser adotadas na hora da codificação que permitem solucionar tais problemas com qualidade e padronização, os famosos Design Patterns ou Padrões de Projeto. Um dos Design Patterns que eu particularmente mais gosto é o Strategy, que se enquadra como um Padrão Comportamental. E, aplicando tal padrão, é possível eliminar os ifs aninhados do código de maneira elegante.

Qual o objetivo do Strategy?

Nesse padrão, nós temos objetos que representam uma estratégia e um contexto que faz o intermédio entre o algoritmo e os objetos de estratégia, cujo comportamento vai variar de acordo com a estratégia utilizada.

Para ilustrar melhor o comportamento e o fluxo quando aplicamos o Strategy em um projeto, criei o diagrama abaixo.

diagrama

Na sua "Classe Inicial", você chama o contexto passando qual estratégia vai ser adotada para a situação em questão, e por sua vez, o contexto vai chamar a estratégia passada. Observe que temos uma Interface de Estratégia, que é implementada em classes concretas, ou seja, essas classes devem implementar o que foi definido no contrato da interface e a implementação pode ser diferente em cada classe. O que significa que estamos aplicando um dos princípios da orientação ao objeto, o Polimorfismo.

Para a situação prática, vou criar uma aplicação do tipo Console, utilizando .NET 7 e C# 11, mas lembre-se, tal padrão pode ser utilizado independente da linguagem ou framework.

Entendendo o Sistema

Estamos criando um sistema para calcular fretes e cada transportadora tem a sua maneira de calcular. Por exemplo, no SEDEX é de um jeito, já na Jadlog é de outra forma, e assim por diante.

Código sem o padrão Strategy:

sem-strategy

De maneira geral, passamos por parâmetro para o nosso método CalculateShipping() qual é a transportadora e nessa função é verificado qual o tipo por if, desviando o fluxo de controle de acordo com o resultado da comparação.

Imagina que agora precisamos adicionar uma nova opção de entrega para cálculo, por Motoboy.. O que teríamos que fazer? Ir no método e adicionar um novo else if para adicionar a nova forma.
E qual é o problema disso? Imagina que a cada vez que precisarmos adicionar ou modificar uma forma de envio, nós teremos que mexer nesse método, e isso acaba ferindo alguns princípios de acoplamento, como o SRP (Princípio da responsabilidade única), e até mesmo o OCP (Principio aberto fechado, onde diz que um método deve estar aberto para extensão mas fechado para modificação).

Executando o programa temos o seguinte resultado no terminal:

Calcular usando DHL

Show! O resultado é o esperado. Mas vamos refatorar esse código utilizando o padrão Strategy?

Passo 1: Criar a estratégia

Na Interface de Estratégia, vamos adicionar o método CalculateShipping() que precisará ser implementado pelas classes derivadas.

interface-strategy

Após a criação da Estratégia, vamos para as classes concretas!

Passo 2: Criar as classes concretas que implementam a estratégia

concreta-sedex

concreta-dhl

concreta-jadlog

Com as classes concretas criadas e herdando da Interface que representa a estratégia, nós precisamos implementar o método descrito no contrato.

Com isso, cada objeto terá sua implementação, não importa como a Jadlog implementa - esse detalhe de implementação fica desacoplado do nosso algoritmo sem impactar nas outras formas de envio.

E, agora, precisamos criar o contexto que vai conectar o nosso fluxo de controle com a estratégia.

Passo 3: Criar o Contexto

contexto

No contexto, nós definimos um campo privado que representa a estratégia que, por sua vez, é atribuído valor no construtor da classe. Após isso, ao invocar o método CalculateShipping() é chamado o CalculateShipping() da estratégia passada no construtor, seja Sedex, Jadlog ou DHL.

Vamos ver como fica a chamada na nossa classe principal?

chamada-strategy

Instanciamos o contexto passando qual estratégia gostaríamos de chamar e, após isso, chamamos o método CalculateShipping() do contexto que, por sua vez, vai chamar o referente ao objeto passado. Vamos executar a aplicação para verificar o resultado?

Calcular usando DHL

Exatamente o mesmo resultado que antes, porém, agora com um código reutilizável, escalável e respeitando alguns dos princípios para as boas práticas de desenvolvimento de software.

Conclusão

Na minha opinião vale super a pena aplicar o padrão Strategy quando identificar um cenário em que é possível a sua utilização. Como visto, em menos de 5 passos foi possível sua implementação, um esforço nesse momento que pode evitar futuros problemas, obtendo ganhos de legibilidade e menores custos de manutenção.

Obrigado por ter lido até aqui e até o próximo artigo!

Carregando publicação patrocinada...
2

Luiz, ficou bugada a visualização das imagens :(

Acabei de fazer um post ensinando como hospedar esses arquivos de forma confiável, dá um bisu lá!

1
1
1
1

Muito interessante, mas fiquei com dúvida em como que exatamente os ifs aninhados vão ser eliminados, pois, ainda assim na função main do seu código é necessário passar o Objeto que será referente a qual transportadora eu irei usar e é exatamente ai que entra minha dúvida, pois vejo que ainda seria necessário um if para saber qual transportadora que será enviada para a classe de contexto, por exemplo:

TransportationContext transportationContext = null;

if(type == "DHL") { 
    transportationContext = new(new DHL());
} ...

Dando uma olhada no Refactoring Guru o exemplo de pseudocódigo que é usado faz exatamente isso que mostrei acima, portanto, mesmo após o uso do Strategy a classe ainda continuaria violando o OCP (Open Closed Principle), pois caso tivesse uma nova transportadora teria que modificar minha classe e adicionar uma condição para ela.

1

Bom... uma forma que pode ajudar na sua duvida? É criar uma classe com dois metodos: um que seja no estilo chave e valor. Eu utilizo um Dictionary onde a chave vai ser um string ou qualquer outro tipo e o value vai ser a interface ITransportationStrategy e o outro metodo para obter o valor.

Ex:

public static class SearchTransport
{
    private static IDictionary<string, ITransportationStrategy> strategy = new 
    Dictionary<string, ITransportationStrategy> 
    {
         {"SEDEX", new Sedex()},
         {"JADLOG", new JadLog()},
         {"DHL", new DHL()}
    };

    public static ITransportationStrategy Get(string transport) =>
        strategy[transport];
}

Então você pode receber qual tipo de frete voce quer dinamicamente...

TransportationContext transportationContext = new(SearchTransport.Get("SEDEX"));
1

Se você programa em Javascript, usa esse padrão quando chama sort, por exemplo.

eg.:

const strategy = (a,b) => b-a;
myArray.sort(strategy);