Aglomerados de 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 anti if campaign 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 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 ifs, já que essa descoberta de qual método executar é feita pela máquina virtual: funcionario.getBonus().
Mas o uso da herança é delicado, e o desenvolvedor deve estar ciente de que ela pode trazer um acoplamento indesejado e 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 reflection. Note como, nesse caso, novamente, nenhum switch ou sequências de ifs precisam ser feitos: ifs são substituídos por polimorfismo:
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 Class.forName("br.com.caelum.taxas." + taxa).newInstance(); 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 if 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 ifs e condicionais podem ser trocados pelo bom uso de polimorfismo, podemos seguir as boas práticas e evitar condicionais complicados e muito aninhados.