Autor: Rafael Felício Fernandes | Publicado: 2011-12-08T09:18:00-08:00



Como não aprender orientação a objetos: o excesso de ifs




Aglomerados de&nbsp;ifs aparecem com frequência, e chegam até a ter um aspecto engraçado. Em alguns casos poder dar a impressão de que estamos usando orientação a objetos, já que cada cláusula costuma envolver a invocação de um método, dependendo do tipo do objeto. Infelizmente, essa sensação é falsa, e chegou até a gerar o conhecido movimento&nbsp;anti if campaign&nbsp;na internet.



O exemplo a seguir mostra uma sequência de ifs que indicam condições distintas de execução:
public double calculaBonus(Funcionario f) {
 if (f.getCargo().equals("gerente")) {
   return f.getVendasDoMes() * 0.05 + getSalario() * 0.1;
 } else if (f.getCargo().equals("diretor")) {
   return f.getVendasDoMes() * 0.05 + getSalario() * 0.2 + (Today.isDecember() ? getSalario() : 0);
 } else {
   return f.getVendasDoMes() * 0.01;
 }
}


Esse tipo de condicional pode ser retrabalhado em objetos (ou funções dependendo da linguagem) que definam o comportamento a ser executado em cada caso. Herança e poliformismo podem (e na maioriam dos casos, devem) ser usados de maneira controlada para&nbsp;alterar um comportamento:
class Funcionario {
 // outros atributos e metodos aqui
 public double getBonus() {
   return vendasDoMes * 0.01;
 }
}
class Gerente extends Funcionario {
 // outros atributos e metodos aqui
 public double getBonus() {
   return vendasDoMes * 0.05 + getSalario() * 0.1;
 }
}


Nesse caso, os responsáveis pelo comportamento são encontrados de acordo com uma condição: o tipo que o objeto representa, sendo que cada subtipo pode redefinir o comportamento do método. Não haverá necessidade de&nbsp;ifs, já que essa descoberta de qual método executar é feita pela máquina virtual:&nbsp;funcionario.getBonus().

Esses ifs não costumam aparecer sozinhos. Assim como o comportamentodo bonus, surgem em breve outros comportamentos com os mesmos ifs em outras partes do sistema:
public double liberaVerba(Funcionario f, Produto produto) {
 if (f.getCargo().equals("gerente")) {
  return produto.getValor() &lt; 5000 || produto.getTipo().equals(Tipo.URGENTE);
 } else if (f.getCargo().equals("diretor")) {
  return produto.getValor() &lt; 10000;
 } else {
  return produto.getValor() &lt; 1000 ||
produto.getTipo().equals(Tipo.USO_DIARIO);
 }
}


Esse tipo de código fica então muito instável: uma mudança decomportamento deve ser alterada em diversos lugares distintos e, piorainda, é muito fácil esquecer um deles ao criar um novo comportamento:alguns dos pontos de multiplos ifs são esquecidos. Ao escrever o mesmo código pela segunda vez, seguimos&nbsp;a prática sugerida no Pragmatic Programmers, de não nos repetir (DRY).

Mas o uso da herança é delicado, e o desenvolvedor deve estar ciente&nbsp;de que ela pode trazer um acoplamento indesejado&nbsp;e&nbsp;suas alternativas. O uso de interfaces se encaixaria aqui com perfeição.

Outros tipos de condições podem determinar qual ação deve ser tomada, como por exemplo o valor de um parâmetro, resultando em uma abordagem que utiliza um mapa. Note como, nesse caso, novamente, nenhum&nbsp;switch&nbsp;ou sequências de&nbsp;ifs precisam ser feitos:&nbsp;ifs são substituídos por polimorfismo:
private Map&lt;String, Aplicador&gt; taxas = new HashMap&lt;String, Aplicador&gt;();

public void processa(String taxa, double juros) {
 impostosRecolhidos += taxas.get(taxa).aplicaComJuros(juros);
}


De todas as variações, a solução do registro (em geral um Map) e a chave é a abordagem mais simples e capaz de separar responsabilidades distintas. Uma outra variação, que remove a necessidade de registrar os itens no mapa, mas abusa de reflection, muitos casos desnecessariamente, é a seguinte:
interface AplicadorDeTaxa {
   double aplicaComJuros(double valor);
 }
}

public void processa(String taxa, double juros) {
 Object instancia = Class.forName("br.com.caelum.taxas." + taxa).newInstance();
 AplicadorDeTaxa aplicador = (AplicadorDeTaxa) instancia;
 impostosRecolhidos += aplicador.aplicaComJuros(juros);
}


A invocação a&nbsp;Class.forName("br.com.caelum.taxas." + taxa).newInstance();&nbsp;pode ainda ser encapsulada em uma Factory, que em vez de buscar por um nome de classe, consultaria anotações ou um arquivo de configuração.

Esses problemas com o&nbsp;if&nbsp;surgem também em outros paradigmas. Em linguagens funcionais é possível atingir o mesmo resultado usando lambdas, ou ainda em procedurais é possível passar ponteiros de funções com abstract data types.

Além dos casos em que&nbsp;ifs e condicionais podem ser trocados pelo bom uso de polimorfismo, podemos seguir as boas práticas e evitar condicionais complicados e muito aninhados.