Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 19, 2022 10:18 pm GMT

Padro de projeto - Strategy

Problema
Imagine o cenrio onde a equipe de desenvolvimento decide criar um sistema responsvel por fazer o clculo do imposto de renda.

Para calcular o imposto de renda necessrio verificar o salrio do funcionrio para ver em qual faixa do imposto de renda ele se encaixa e depois disso aplicar o desconto. Sendo que o desconto o salrio multiplicado pelo percentual da alquota e subtrado pela parcela a deduzir.

Para compreender um pouco mais quais dos dados so relevantes para aplicar ou no o desconto segue a tabela do imposto de renda:

Image description

Fonte: Receita federal

Olhando para essa tabela j comeamos a imaginar estruturas de decises e desenharmos na nossa mente algo semelhante ao seguinte cdigo:

  Scanner teclado = new Scanner(System.in).useLocale(Locale.US);        double salario = teclado.nextDouble();        if (salario <= 1903.98) {            System.out.println("nao ha deducao fiscal");        } else if (salario >= 1903.99 && salario <= 2826.65) {            System.out.println("A deducao fiscal e de 7,5%");            System.out.println("Voce deve deduzir o valor de " + ((salario * 0.075) - 142.80) + " reais");        } else if (salario >= 2826.66 && salario <= 3751.05) {            System.out.println("A deducao fiscal e de 15%");            System.out.println("Voce deve deduzir o valor de " + ((salario * 0.15) - 354.80) + " reais");        } else if (salario >= 3751.06 && salario <= 4664.68) {            System.out.println("A deducao fiscal e de 22,5%");            System.out.println("Voce deve deduzir o valor de " + ((salario * 0.225) - 636.13) + " reais");        } else if (salario >= 4664.68) {            System.out.println("A deducao fiscal e de 27,5%");            System.out.println("Voce deve deduzir o valor de " + ((salario * 0.275) - 869.36) + " reais");        }

Essa uma abordagem para chegar ao resultado, porm h alguns problemas nesse cdigo, o primeiro estarmos usando o tipo double para uma varivel que trabalhar com valores monetrios e o segundo a probabilidade desse cdigo crescer mais e termos uma estrutura de deciso muito grande.

Mas qual o malefcio com estruturas de decises que tendem a crescer muito?
Um malefcio dessas estruturas ficarem muito grandes que caso precisem implementar uma nova funcionalidade, as chances de quebrar as funcionalidades existentes grande. Isso acontece devido ao alto acoplamento.

Como podemos alterar esse cdigo?
Ao invs de usarmos estruturas de decises que tendem a ficar cada vez maiores, podemos optar usar uma ferramenta muito poderosa que um dos pilares da programao orientada a objetos, o polimorfismo.

Para refatorar esse cdigo removendo as estruturas de decises o primeiro passo seria criar um mtodo que vai ser o responsvel pelo clculo em si e outro mtodo booleano responsvel por ver se a regra se aplica para aquele salrio.

public interface CalculadoraImpostoDeRenda {    boolean deveAplicarPara(BigDecimal salario);    BigDecimal efetuarCalculo(BigDecimal salario);}

Nesse exemplo preferi criar uma interface com esses mtodos, pois o nosso prximo passo ser criar diversas classes, cada uma representando um "if" que tinhamos e elas implementaro a interface CalculadoraImpostoDeRenda.

A seguir o exemplo de duas classes:

public class ImpostoDeRendaIsento implements CalculadoraImpostoDeRenda {    private final static BigDecimal VALOR_MAXIMO = new BigDecimal("1903.98");    @Override    public boolean deveAplicarPara(BigDecimal salario) {        return salario.compareTo(VALOR_MAXIMO) <= 0;    }    @Override    public BigDecimal efetuarCalculo(BigDecimal salario) {        if(!deveAplicarPara(salario)) throw new RuntimeException("Salario no se aplica para essa regra");        return ZERO;    }}
public class ImpostoDeRendaMedioBaixo implements CalculadoraImpostoDeRenda {    private final static BigDecimal VALOR_MINIMO = new BigDecimal("1903.99");    private final static BigDecimal VALOR_MAXIMO = new BigDecimal("2826.65");    @Override    public boolean deveAplicarPara(BigDecimal salario) {        return salario.compareTo(VALOR_MINIMO) >= 0 && salario.compareTo(VALOR_MAXIMO) <= 0;    }    @Override    public BigDecimal efetuarCalculo(BigDecimal salario) {        if(!deveAplicarPara(salario)) throw new RuntimeException("Salario no se aplica para essa regra");        return (salario.multiply(new BigDecimal("0.075"))                .subtract(new BigDecimal("142.80"))                .setScale(2, RoundingMode.HALF_UP));    }}

Nessa refatorao j estamos usando o BigDecimal para representar o salrio por conta da falta de preciso dos pontos flutuantes(mais sobre esse assunto nesse link aqui), e por isso estamos fazendo comparaes utilizando compareTo.

Cada classe verifica se o salrio se aplica para aquela regra e depois efetua de fato o clculo de acordo com a alquota.

Temos que fazer isso para os cinco casos que existem na tabela do imposto de renda.

Depois disso, fazemos a criao de um enum que ter esses cinco casos e ter um mtodo (calcularImpostoDeRenda) que ser o responsvel por de fato fazer o clculo correto de acordo com o salrio.

public enum TipoDoImpostoDeRenda {    ISENTO(new ImpostoDeRendaIsento()),    MEDIO_BAIXO(new ImpostoDeRendaMedioBaixo()),    MEDIO_ALTO(new ImpostoDeRendaMedioAlto()),    ALTO(new ImpostoDeRendaAlto()),    TETO(new ImpostoDeRendaTeto());    private final CalculadoraImpostoDeRenda calculadoraImpostoDeRenda;    TipoDoImpostoDeRenda(CalculadoraImpostoDeRenda calculadoraImpostoDeRenda) {        this.calculadoraImpostoDeRenda = calculadoraImpostoDeRenda;    }    public static BigDecimal calcularImpostoDeRenda(BigDecimal salario) {        return Arrays.stream(TipoDoImpostoDeRenda.values())                .filter(t -> t.deveAplicarPara(salario))                .findFirst()                .map(i -> i.efetuarCalculo(salario))                .orElseThrow(() -> new RuntimeException("Tipo de imposto de renda no encontrado"));    }    private boolean deveAplicarPara(BigDecimal salario) {        return calculadoraImpostoDeRenda.deveAplicarPara(salario);    }    private BigDecimal efetuarCalculo(BigDecimal salario) {        return calculadoraImpostoDeRenda.efetuarCalculo(salario);    }}

Perceba que a partir desse momento passamos a usar a interface para fazer essa lgica, ou seja, caso outro filtro do imposto venha a existir ns s vamos adicionar outra classe e mais um valor nesse enum.

Agora s fazer a chamada desse mtodo, temos a classe PessoaFisica que contm o atributo salrio:

public class PessoaFisica {    private BigDecimal salario;    public PessoaFisica(String salario) {        this.salario = new BigDecimal(salario);    }    public BigDecimal calcularSalarioLiquido() {        return TipoDoImpostoDeRenda.calcularImpostoDeRenda(this.salario);    }}

Depois disso podemos criar uma main para testar se o nosso cdigo est funcionando:

public class Teste {    public static void main(String[] args) {        PessoaFisica pessoaFisica = new PessoaFisica("2423.00");        BigDecimal salarioLiquido = pessoaFisica.calcularSalarioLiquido();        System.out.println(salarioLiquido);    }}

Dessa forma refatoramos o cdigo utilizando o padro de projeto strategy!

Oque o padro de projeto strategy?
A refatorao que acabamos de fazer foi feita com base em um padro de projeto chamado strategy.

O strategy um padro de projetos que deve ser utilizado em estruturas de decises como if e switch que tendem a crescer bastante.

A soluo proposta pelo padro strategy encapsular o algoritmo responsvel por variar e isso pode ser feito de algumas maneiras (no nosso exemplo foi na interface CalculadoraImpostoDeRenda).

O padro strategy indicado quando temos um parmetro e sabemos que aquela regra ser aplicada naquele determinado parmetro. No exemplo apresentado sabiamos exatamente quais eram os diferentes tipo de impostos que tinhamos.

Talvez, nesse caso, especfico, nem precisamos do Strategy. possvel pensar em uma regra geral de clculo, deixando-a no enum e evitando criar as classes: ImpostoDeRendaIsento, ImpostoDeRendaMedioBaixo, ImpostoDeRendaMedioAlto, ImpostoDeRendaAlto, ImpostoDeRendaTeto e at mesmo a interface CalculadoraImpostoDeRenda.

Dessa forma:

enum TipoDoImpostoDeRenda {    ISENTO(new BigDecimal("-1"), new BigDecimal("1903.98"), BigDecimal.ZERO, BigDecimal.ZERO),    MEDIO_BAIXO(new BigDecimal("1903.99"), new BigDecimal("2826.65"), new BigDecimal("0.075"), new BigDecimal("142.80")),    MEDIO_ALTO(new BigDecimal("2826.66"), new BigDecimal("3751.05"), new BigDecimal("0.150"), new BigDecimal("354.80")),    ALTO(new BigDecimal("3751.06"), new BigDecimal("4664.68"), new BigDecimal("0.225"), new BigDecimal("636.13")),    TETO(new BigDecimal("4664.69"), new BigDecimal(Integer.MAX_VALUE), new BigDecimal("0.275"), new BigDecimal("869.36"));    private final BigDecimal valorMinimo;    private final BigDecimal valorMaximo;    private final BigDecimal aliquota;    private final BigDecimal parcelaADeduzir;    TipoDoImpostoDeRenda(BigDecimal valorMinimo, BigDecimal valorMaximo, BigDecimal aliquota, BigDecimal parcelaADeduzir) {      this.valorMinimo = valorMinimo;      this.valorMaximo = valorMaximo;      this.aliquota = aliquota;      this.parcelaADeduzir = parcelaADeduzir;    }    public static BigDecimal calcularImpostoDeRenda(BigDecimal salario) {        return Arrays.stream(TipoDoImpostoDeRenda.values())                .filter(t -> t.deveAplicarPara(salario))                .findFirst()                .map(i -> i.efetuarCalculo(salario))                .orElseThrow(() -> new RuntimeException("Tipo de imposto de renda no encontrado"));    }    private boolean aplica(BigDecimal salario) {      return salario.compareTo(valorMinimo) >= 0 && salario.compareTo(valorMaximo) <= 0;    }    private BigDecimal calcula(BigDecimal salario) {      return (salario.multiply(aliquota)                .subtract(parcelaADeduzir)                .setScale(2, RoundingMode.HALF_UP));    }}

Dessa forma manteramos apenas as classes PessoaFisica e Teste, deixando o enum um pouco mais complexo de se entender, no entanto, removendo vrias classes que usamos para nos auxiliar nesse padro.

Vantagens do strategy

  • Um cdigo bem mais limpo, pois voc isola os detalhes da implementao;

  • Um algoritmo que pode ser alterado mais facilmente, mexendo somente na classe responsvel;

  • Princpio SOLID Aberto/fechado, pois se insere novas classes sem mudar o contexto.

Desvantagens do strategy

  • Aumento na complexidade do cdigo, pois precisa ser criada a instncia de diferentes classes. Devido a isso sempre importante ter critrio na utilizao do pattern.

Aqui est o link do cdigo completo do exemplo:
https://github.com/mariasantosdev/calculo-imposto-de-renda-strategy

Se quiser ir alm e ver tambm outro exemplo onde o strategy aplicado em um enum:
https://github.com/mariasantosdev/jogo-dinossauro

Materiais utilizados de apoio para escrever o artigo
https://www.casadocodigo.com.br/products/livro-design-patterns
https://refactoring.guru/pt-br/design-patterns/strategy


Original Link: https://dev.to/mariasantosdev/padrao-de-projeto-strategy-1aod

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To