Elegância e Desempenho em Estruturas de Decisão

Salve, salve!
Estou iniciando no mundo dos objeto e, resolvendo uma lista de exercícios, uma questão me chamou atenção…

Foi pedido que se calcule o IMC corporal de um indivíduo. Há uma tabela com faixas predefinidas e distintas para homens e mulheres.
Cheguei as duas soluções abaixo utilizando estruturas de decisão:

if ((sexo == 'F' && imc < 19.1) || (sexo == 'M' && imc < 20.7)) { System.out.println("Abaixo do peso."); } else if ((sexo == 'F' && imc <= 25.8) || (sexo == 'M' && imc <= 26.4)) { System.out.println("Peso ideal."); } else if ((sexo == 'F' && imc <= 27.3) || (sexo == 'M' && imc <= 27.8)) { System.out.println("Um pouco acima do peso."); } else if ((sexo == 'F' && imc <= 32.3) || (sexo == 'M' && imc <= 31.1)) { System.out.println("Acima do peso ideal."); } else { System.out.println("Obeso."); }

if (sexo == 'F') { if (imc < 19.1) { System.out.println("Abaixo do peso."); } else if (imc <= 25.8) { System.out.println("Peso ideal."); } // continua... } else { if (imc < 20.7) { System.out.println("Abaixo do peso."); } else if (imc <= 26.4) { System.out.println("Peso ideal."); } // continua... }

Entra as duas, qual terá um melhor desempenho? Confesso que a segunda me pareceu mais legível…
Ou, pensando um pouco além, vale mais à pena implementar homem e mulher como subclasses de indivíduo e trabalhar com polimorfismo?

Acredito que seja a segunda já que no caso de ser obeso ele passará por menos testes de condição.

O interessante do segundo caso é que remove aquela verificação do “F” ou do “M” em praticamente todo o encadeamento das condições.
Em termos de perfomance, difícil dizer. Para um caso isolado com este, acredito que seria insignificante a diferença.

Mas já que tocou no assunto de Polimorfismo, sim, é a melhor solução. Dá uma olhada no exemplo usando o padrão Strategy.
http://www.guj.com.br/java/55885-como-nao-utilizar-if-ou-switch#293436

Achei excelente quando o conheci e obviamente, apliquei no sistema pois se encaixou perfeitamente com uma de minhas necessidades.
Abraços.

[quote=lavreh]Salve, salve!
Estou iniciando no mundo dos objeto e, resolvendo uma lista de exercícios,
[/quote]

O interessante é que vc não usou objetos.

Vamos traduzir isto em objetos:

Foi pedido que se calcule o IMC corporal de um indivíduo. Há uma tabela com faixas predefinidas e distintas para homens e mulheres.

então vamos começar com IMC

O IMC é um numero, mas no seu cado vc quer a classificação. Isto pede um enum


public enum ICMClassification {

    ABAIXO_PESO,
    PESO_IDEAL,
    UM_POUCO_ACIMA,
    ACIMA_PESO,
    OBESO;
}

agora “tabela de faixas”

public class Faixa {
    
 private double menor;
 private double maior;

 public Faixa (double menor, double maior, ICMClassification  classification){
     this.menor = menor;
     this.maior = maior;
     this.classification = classification;
}

  public ICMClassification  getClassification(){
         return classification;
  }

  public boolean contains(double value){
       return value >= menor || value <= maior;
  } 
}

// tabela de faixas
public class Tabela {

   private List<Faixa> faixas = new ArrayList<Faixa>();


   public void addFaixa(Faixa faixa){
            this.faixas.add(faixa);
  }

   public ICMClassification  getClassificacao(double icm){

              for (Faixa faixa : faixas){
                         if (faixa.contains(icm)){
                                 return faixa.getClassification();
                        }
              }

              // se chegou aqui nenhuma faixa foi escolhida, o que é um erro
             throw new IllegalArgumentException(icm + " não está em nenhuma faixa" );
   }
}

Agora precisamos de um objeto que racterize o sujeito


public enum Genero {
   FEMENINO,
   MASCULINO
}

public class Sujeito {

      private Genero genero;
       private double icm;

       public Sujeito ( Genero genero , double icm){
             this.genero = genero; 
             this.icm = icm;
       }

       public  Genero  getGenero (){
       return genero;
      }

     public  double getICM (){
       return icm;
      }
}

Agora juntamos tudo num ICMClassificationCalculator


public class ICMClassificationCalculator {

       Map<Genero, Tabela > tabelas = new HashMap<Genero, Tabela>();
       
       public  ICMClassificationCalculator (){
              // monta as tabelas com os valores

              Tabela masculina = new Tabela();

                 masculina.addFaixa(new Faixa(0, 20.7, ABAIXO_PESO));
                 // etc. .. 

                tabela.put(Genero.MASCULINO, masculina);

             // semelhante para a femenina
       }

      public ICMClassification calculateFor(Sujeito sujeito){
                 return tabelas.get(sujeito.getGenero()).getClassificacao(sujeito.getICM());
      } 

}

E usaria assim :



 Sujeito sujeito  = new Sujeito(Genero.MASCULINO, 23.8);

 ICMClassificationCalculator calculator = new ICMClassificationCalculator ();

  ICMClassification classificao = calulator.calculateFor(sujeito);

  switch (classificao ){
  case ABAIXO_PESO:
   System.out.println("Abaixo do peso."); 
   break; 
 case PESO_IDEAL:
   System.out.println("Peso ideal.");  
   break; 
  // etc..
 } 
   

Veja que vc só usa um if . O truque é estruturar o codigo para usar for e map e métodos boolenos como contains.
Este modelo é OO, é elegante e o desempenho usando ifs e for é depresável em relação à legibilidade e entendimento.
Usando enum vc se livra de usar Strings para opções (o que tornar o codigo POS - programação orientada a string) e permite um melhor entendimento.

Vc pode achar que é sobre-engenharia, mas apenas usamos o processo padrão. Analizar as entidades, trasnformá-las em classes e compor as responsabilidades.
Poderiamos agora simplificar algumas coisas, mas o objetivo aqui é vc entender o que é OO. usar ifs não é OO.

if ((sexo == 'F' && imc < 19.1) || (sexo == 'M' && imc < 20.7)) {  
    System.out.println("Abaixo do peso.");  
} else if ((sexo == 'F' && imc <= 25.8) || (sexo == 'M' && imc <= 26.4)) {  
    System.out.println("Peso ideal.");  
} else if ((sexo == 'F' && imc <= 27.3) || (sexo == 'M' && imc <= 27.8)) {  
    System.out.println("Um pouco acima do peso.");  
} else if ((sexo == 'F' && imc <= 32.3) || (sexo == 'M' && imc <= 31.1)) {  
    System.out.println("Acima do peso ideal.");  
} else {  
    System.out.println("Obeso.");  
}

me lembra um programa Perl, mais ou menos assim ( http://perldoc.perl.org/perlsyn.html#Simple-Statements ) :

print "Abaixo do peso" if (sexo == "F" && imc < 19.1) || (sexo == "M" && imc < 20.7);
print "Peso ideal" if (sexo == "F" && imc <= 25.8) || (sexo == "M" && imc <= 26.4);
...
print "Obeso" unless .... (fiquei com preguiça de escrever a condição)

A primeira vez que vi Perl achei que essa era uma maneira mais intuitiva de usar o “if” porque é assim que se fala em português (“misture uma colher de sopa de sal SE a sopa não estiver muito salgada”).

[quote=entanglement] if ((sexo == 'F' && imc < 19.1) || (sexo == 'M' && imc < 20.7)) { System.out.println("Abaixo do peso."); } else if ((sexo == 'F' && imc <= 25.8) || (sexo == 'M' && imc <= 26.4)) { System.out.println("Peso ideal."); } else if ((sexo == 'F' && imc <= 27.3) || (sexo == 'M' && imc <= 27.8)) { System.out.println("Um pouco acima do peso."); } else if ((sexo == 'F' && imc <= 32.3) || (sexo == 'M' && imc <= 31.1)) { System.out.println("Acima do peso ideal."); } else { System.out.println("Obeso."); }
me lembra um programa Perl, mais ou menos assim ( http://perldoc.perl.org/perlsyn.html#Simple-Statements ) :

print "Abaixo do peso" if (sexo == "F" && imc < 19.1) || (sexo == "M" && imc < 20.7);
print "Peso ideal" if (sexo == "F" && imc <= 25.8) || (sexo == "M" && imc <= 26.4);
...
print "Obeso" unless .... (fiquei com preguiça de escrever a condição)

A primeira vez que vi Perl achei que essa era uma maneira mais intuitiva de usar o “if” porque é assim que se fala em português (“misture uma colher de sopa de sal SE a sopa não estiver muito salgada”). [/quote]

É do Perl que o Ruby tirou esta idéia do if depois do statement então? Interessante. (=

nel, isoladamente realmente parece insignificante. Porém a ideia é analisar o conceito, o qual talvez possa ser aplicado em algoritmos mais complexos.

Revendo os códigos, a segunda solução seria um exemplo clássico do “dividir e conquistar”. Conseguindo, somente na comparação dos gêneros, reduzir na pior das hipóteses, 8 testes para somente 1. Corrijam-me caso eu esteja errado nesta conclusão…

Mas tudo isso não passa de devaneios de uma mente acostumada a pensar estruturadamente.
Realmente polimorfismo parece cair como uma luva neste problema… Preciso estudar isto!

PS. Alguém poderia me indicar fontes sobre análise e otimização de algoritmos em Java?

sergiotaborda, não tenho palavras para agradecer pela aula.

Devo confessar que esse confronto entre paradigmas está sendo um tanto traumatizante… Mas seu post foi realmente muito elucidativo!
A princípio, me pareceu mais complexo do que seria necessário. Mas após rabiscar um diagrama de classes e, passar a pensar em termo de objetos, as coisas começaram a fazer sentido para mim e pude perceber algumas das aclamadas vantagens da orientação a objetos.

Valeu!
Força e Luz!