Artigos de Tecnologia e Negócios

Reduzindo de N ifs para nenhum com Strategy em Java

Alex Felipe
Alex Felipe

Estou desenvolvendo um sistema para computar todas as vendas de uma empresa. Atualmente, estou representando cada funcionário da seguinte forma:


public class Funcionario {

private String nome; private double salario; private Cargo cargo;

//métodos

}

O atributo cargo é um enum que listará todos os cargos diferentes do sistema:


public enum Cargo {

ATENDENTE, VENDEDOR, GERENTE;

}

Além da representação do funcionário, eu também fiz a representação de uma venda, que possui um funcionário e o valor da venda:


public class Venda {

private final Funcionario funcionario; private final double valor;

public Venda(Funcionario funcionario, double valor) { this.funcionario = funcionario; this.valor = valor; }

}

Porém, cada vez que uma compra for realizada, eu preciso calcular a comissão do funcionário de acordo com o seu cargo, por exemplo, se o funcionário for um atendente, a comissão será de 10% do valor da venda, se for vendedor 15% + 5 reais e gerente 20% + 10 reais. Então vamos criar o método calculaComissao() na classe Venda:


public class Venda {

//atributos e construtor

public void calculaComissao(){ //calcula a comissão }

}

Certo, agora vamos implementar o método para calcular a comissão. Bom, podemos pegar o cargo do funcionário e fazer um if para verificar qual é o cargo dele e então realizar o calculo da sua comissão:


public class Venda {

private final Funcionario funcionario; private final double valor; //construtor

public void calculaComissao() {

double comissao = 0.0;

Cargo cargo = this.funcionario.getCargo(); if (cargo.equals(Cargo.ATENDENTE)) { // comissão de 10% comissao = valor \* 0.1; } else if (cargo.equals(Cargo.VENDEDOR)) { // comissão de 15% + 5 reais comissao = valor \* 0.15 + 5; } else if (cargo.equals(Cargo.GERENTE)) { // comissão de 20% + 10 reais comissao = valor \* 0.20 + 10; } }

}

Conseguimos calcular, porém precisamos retornar a comissão da venda, então vamos alterar a assinatura de void para double e então, retornamos a variável comissao:


public class Venda {

//construtor e atributos

public double calculaComissao() {

double comissao = 0.0;

Cargo cargo = this.funcionario.getCargo(); if (cargo.equals(Cargo.ATENDENTE)) { comissao = valor \* 0.1; } else if (cargo.equals(Cargo.VENDEDOR)) { comissao = valor \* 0.15 + 5; } else if (cargo.equals(Cargo.GERENTE)) { comissao = valor \* 0.20 + 10; } return comissao; }

}

Ótimo, agora precisamos fazer uma simulação para verificar se está funcionando! Então vamos lá:


public class Main {

public static void main(String\[\] args) {

Funcionario atendente = new Funcionario(); atendente.setNome("Maria da Silva"); atendente.setSalario(1200.00); atendente.setCargo(Cargo.ATENDENTE);

Venda novaVenda = new Venda(atendente, 200.0);

System.out.println("valor da comissão: " + novaVenda.calculaComissao());

}

}

Verificando o resultado:

 > valor da comissão: 20.0

Para um atendente o valor da comissão é de 10%, e 10% de 200 é realmente 20! Então está funcionando como esperado! E se tentarmos a mesma venda com um cargo de vendedor?

 > valor da comissão: 35.0

15% de 200 é 30, mais 5 reais fica 35! Por enquanto tudo certo! Vamos verificar o último cargo, ou seja, o de gerente:

 > valor da comissão: 50.0

Excelente! Conforme o esperado, foi impressa uma comissão de 20% que equivale 40, somou 10 e resultou em 50! Aparentemente a minha implementação está impecável! Sem nenhum problema! Só que não! :(

Entendendo o problema dos ifs

Consegue imaginar o que está sendo tão problemático? Vejamos novamente a nossa implementação da calculaComissao():


public class Venda {

//atributos e construtor

public double calculaComissao() {

double comissao = 0.0;

Cargo cargo = this.funcionario.getCargo(); if (cargo.equals(Cargo.ATENDENTE)) { comissao = valor \* 0.1; } else if (cargo.equals(Cargo.VENDEDOR)) { comissao = valor \* 0.15 + 5; } else if (cargo.equals(Cargo.GERENTE)) { comissao = valor \* 0.20 + 10; }

return comissao; }

}

Veja que, para cada cargo, estamos fazendo um if que identifica qual cargo se refere, e então, calculamos a sua comissão. Vamos supor que a empresa que solicitou uma adição de 3 cargos diferentes, que são: ajudante (8% + 1 real), recepcionista (5%) e diretor (25% + 20 reais):


public enum Cargo {

ATENDENTE, VENDEDOR, GERENTE, AJUDANTE, RECEPCIONISTA, DIRETOR;

}

Como ficaria o nosso método calculaComissao()? Vejamos:


public class Venda {

//métodos, atributos e construtor

public double calculaComissao() {

double comissao = 0.0;

Cargo cargo = this.funcionario.getCargo(); if (cargo.equals(Cargo.ATENDENTE)) { comissao = valor \* 0.1; } else if (cargo.equals(Cargo.VENDEDOR)) { comissao = valor \* 0.15 + 5; } else if (cargo.equals(Cargo.GERENTE)) { comissao = valor \* 0.20 + 10; } else if (cargo.equals(Cargo.AJUDANTE)) { comissao = valor \* 0.08 + 1; } else if (cargo.equals(Cargo.RECEPCIONISTA)) { comissao = valor \* 0.05; } else if (cargo.equals(Cargo.DIRETOR)) { comissao = valor \* 0.25 + 20; }

return comissao; }

}

Observe que o nosso método calculaComissao(), atualmente, tende a crescer e muito! Com certeza essa é uma má prática, pois se um dia adicionarmos um novo cargo e esquecermos de adicionar nesse conjunto gigantesco de ifs e elses, o nosso sistema apresentará um bug facilmente...

Além disso, e se quisermos reutilizar esse método em uma outra classe qualquer? Teremos que fazer um copy e paste de todas essas condições e, se um dia mudar a regra de negócio e tiver um reajuste na comissão? Teremos que mudar em todos os pontos do nosso sistema que está utilizando esse tipo de implementação...

Veja quanta complicação! Com certeza é uma solução trabalhosa e não tão inteligente! Sabendo desses problemas, como poderíamos resolver isso?

Separando as responsabilidades

Que tal isolarmos a responsabilidade de calcular a comissão para cada cargo? Mas como assim isolar a responsabilidade? Exatamente isso, fazer com que o próprio cargo saiba calcular a sua comissão, ou seja, podemos fazer com o que cada enum já implemente um método calculaComissao()!


public enum Cargo {

ATENDENTE { public double calculaComissao(double valor){ return valor \* 0.1; } }, VENDEDOR,GERENTE,AJUDANTE,RECEPCIONISTA,DIRETOR;

}

Repare que, ainda existe um grande problema, pois da forma que está atualmente, o nosso enum compila nesse instante, porém quebra todos os pontos do sistema que o chama, pois não é garantido que todos os valores do enum tenham esse método!

Precisamos, de alguma forma, garantir que todos os cargos tenham um método calculaComissao()!

Garantido a chamada de métodos por meio de interface

E agora? Como podemos fazer isso? Da mesma forma que em classes, nós podemos fazer com que o enum implemente uma interface que obrigue a implementação do método calculaComissao()! Então vamos criar essa interface:


public interface Comissao {

public double calculaComissao(double valor); }

Agora que temos a interface Comissao, basta implementá-la no enum e sobrescrever o método para todos os cargos:


public enum Cargo implements Comissao {

ATENDENTE { @Override public double calculaComissao(double valor) { return valor \* 0.1; } }, VENDEDOR { @Override public double calculaComissao(double valor) { return valor \* 0.15 + 5; } }, GERENTE { @Override public double calculaComissao(double valor) { return valor \* 0.20 + 10; } }, AJUDANTE { @Override public double calculaComissao(double valor) { return valor \* 0.08 + 1; } }, RECEPCIONISTA { @Override public double calculaComissao(double valor) { return valor \* 0.05; } }, DIRETOR { @Override public double calculaComissao(double valor) { return valor \* 0.25 + 20; } };

}

Certo, definimos o método calculaComissao() para cada cargo, e como ficaria o método calculaComissao() da classe Venda? Vejamos o que muda:


private final Funcionario funcionario; private final double valor;

public Venda(Funcionario funcionario, double valor) { this.funcionario = funcionario; this.valor = valor; }

public double calculaComissao() {

double comissao = 0.0;

Cargo cargo = this.funcionario.getCargo(); comissao = cargo.calculaComissao(valor);

return comissao;

}

}

Cade aqueles ifs e elses??? Será que ainda funciona como antes? Vamos testar novamente, para todos os cargos para uma compra no valor de 200:

Atendente (10%): ```

valor da comissão: 20.0


Vendedor (15% + reais): ```
 > valor da comissão: 35.0

Gerente (20% + 10 reais): ```

valor da comissão: 50.0


Ajudante (8% + 1 real): ```
 > valor da comissão: 17.0

Recepcionista (5%): ```

valor da comissão: 10.0


Diretor (25% + 20 reais): ```
 > valor da comissão: 70.0

Excelente! Está funcionando conforme o esperado, porém agora, nós podemos calcular a comissão de um funcionário chamando apenas 1 método e em qualquer lugar!

E se agora precisarmos fazer aquele reajuste?

Basta alterar apenas no enum e funcionará da mesma forma para todas as classes que chamarem o método calculaComissao()!

Todo conceito por de trás da nossa implementação

A solução que utilizamos não é uma invenção minha, isso é um Design Pattern (na tradução, padrão de projeto) chamado Strategy que, por meio de polimorfismo, nos permite adicionar/alterar implementações de um método em comum, deixando-as encapsuladas, sem que impacte as chamadas realizadas pelo cliente.

Em outras palavras, podemos fazer diversas variações de um mesmo método em comum (por exemplo, o calculaComissao()) que precise de um comportamento diferente para cada situação, nesse caso, o calculo de comissão para cada cargo diferente!

A implementação demonstrada no post é uma das possíveis implementações do Strategy, ou seja, existem diversas outras!

Isso significa que é muito mais importante entender o conceito utilizado para resolver o problema com o Strategy do que apenas a implementação, ou melhor dizendo, identificar uma situação que apresenta muitas variações para uma determinada funcionalidade que por sua vez, são condicionais, como foi o caso do calculo de comissão ser diferente para cada cargo diferente, e então aplicar o Strategy!

E aí, o que achou do Strategy? Nunca havia pensado que os if algum dia poderiam sumir? Compartilhe conosco suas impressões nos comentários ;)

Quer aprender mais sobre Design Pattern? Na Alura, temos o curso de Design Pattern em Java que demonstra tanto o Strategy como outros Design Patterns, explicando como cada um funciona e aonde podemos aplicar cada um deles!

Artigos de Tecnologia e Negócios