Reduzindo de N ifs para nenhum com Strategy em Java
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();
tendente.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 if
s e else
s, 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 if
s e else
s??? 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 artigo é 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?
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!