[quote=rogelgarcia]Pessoal me pediu que colocasse o código comentado sobre a resolução para evitar IFS.
Abaixo do código colocarei uma explicação:
[code]
/*
- Esse sistema tem dois conceitos.
- Etapa da vida, associado a idade da pessoa.
- Classificacoes, associadas a qualquer informação da pessoa.
-
- A idéia é resolver essa problemática sugerida pelo colega Luiz Augusto Prado sem o uso de IFS:
if( dadosDoUsuario.idade < 18 )
{
os dados vão para o objeto a
}
else if( dadosDoUsuario.idade >= 18 && dadosDoUsuario.idade<55 )
{
os dados vão para o objeto b
}
else if( dadosDoUsuario.idade >= 55 && dadosDoUsuario.idade<120 || dadosDoUsuario.numeroFilhos>2 )
{
os dados vão para o objeto c
}
else
{
os dados vão pro limbo
}
*/
/**
-
Esse enum representa as 4 etapas possíveis da vida para esse sistema.
*/
enum EtapaDaVida {
JOVEM(18),
ADULTO(55),
IDOSO(120),
HIGHLANDER(Integer.MAX_VALUE);
//um ENUM pode ter atributos
int idadeMaxima;
//que sao atribuidos através do construtor
//após atribuido, o valor é imutável (lembre-se ENUMs são imutáveis)
private EtapaDaVida(int idade){
this.idadeMaxima = idade;
}
/**
- Método que retorna a Etapa da vida para determinada idade
*/
public static EtapaDaVida get(int idade){
for (EtapaDaVida etapa : values()) { //values() = [JOVEM, ADULTO, IDOSO, HIGHLANDER]
if(idade <= etapa.idadeMaxima){
// se a idade for menor que a idade máxima da etapa significa que
// é essa a etapa relacionada a idade
return etapa;
}
}
//se não encontrar etapa relacionada com a idade retorna um erro
throw new RuntimeException(“resultado inesperado”);
}
/**
- Método que retorna a Etapa da vida para determinada data.
- Irá calcular quantos anos a pessoa tem através de Utils.getIdade
-
@param data
-
@return
*/
public static EtapaDaVida get(Date data){
return get(Utils.getIdade(data));
}
}
class Usuario {
Date nascimento;
int numeroFilhos;
}
/**
-
Esse enum representa as 4 classificações do sistema.
*/
enum Classificacao {
//cada enum será construido com um classificador
A(new ClassificadorA()),
B(new ClassificadorB()),
C(new ClassificadorC()),
LIMBO(new ClassificadorLimbo());
Classificador classificador;
Classificacao(Classificador classificador){
this.classificador = classificador;
}
public boolean isClassificacao(Usuario u){
//a classificacao utiliza o classificador para determinar se o usuario faz parte dela
return classificador.isClassificacao(u); //o classificador é uma das classes implementadas abaixo
}
public static Classificacao get(Usuario usuario) {
for (Classificacao c : values()) {//values() = [A, B, C, LIMBO]
//para cada Classificacao (enum), verificamos se o usuario faz parte dela
if(c.isClassificacao(usuario)){
return c; //retorna a classificacao do usuario
}
}
throw new RuntimeException(“resultado inesperado”);
}
}
/**
- Interface que representa um classificador
/
interface Classificador {
/
- Retorna true se esse classificador considera que o usuário faz parte dele
*/
public boolean isClassificacao(Usuario usuario);
}
class ClassificadorA implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.JOVEM;
}
}
class ClassificadorB implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.ADULTO;
}
}
class ClassificadorC implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return EtapaDaVida.get(usuario.nascimento) == EtapaDaVida.IDOSO || usuario.numeroFilhos > 2;
}
}
class ClassificadorLimbo implements Classificador{
public boolean isClassificacao(Usuario usuario) {
return true;
}
}
public class Example2 {
public static void main(String[] args) {
//monta a estrutura de dados que vai receber a distribuicao
Map<Classificacao, List<Usuario>> grupos = new HashMap<Classificacao, List<Usuario>>();
for(Classificacao etapa: Classificacao.values()){
//para cada chave (Classificacao), criamos uma nova lista
grupos.put(etapa, new ArrayList<Usuario>());
}
//aqui é onde viria o ninho de ifs
for (Usuario usuario : getListaUsuarios()) {
//retornamos a classificacao do usuario
Classificacao classificacao = Classificacao.get(usuario);
//adicionamos o ususario a classificacao correta
grupos.get(classificacao).add(usuario);
}
}
public static ArrayList<Usuario> getListaUsuarios() {
return new ArrayList<Usuario>(); //aqui seria criada a lista de usuarios a serem organizados
}
}
class Utils {
//método utilitário para retornar a idade de uma data
public static int getIdade(Date data){
Calendar hoje = Calendar.getInstance();
Calendar nascimento = Calendar.getInstance();
nascimento.setTime(data);
int idade = hoje.get(Calendar.YEAR) - nascimento.get(Calendar.YEAR);
nascimento.set(Calendar.YEAR, hoje.get(Calendar.YEAR));
if(nascimento.compareTo(hoje) > 0){
//nao fez aniversario esse ano
idade--;
}
return idade;
}
}[/code]
Não se atente ao número de linhas do código. O importante aqui é a análise da arquitetura criada. Qual a diferença entre esse código e um conjunto de ifs do ponto de vista de organização do sistema e não de esforço para criar tal solução.
Esse sistema possui dois conceitos: Etapa da vida e Classificação.
Os objetos do tipo Usuario, serão organizados em uma lista de acordo com sua classificação. Essa classificação envolve a descoberta da etapa da vida de um usuário.
Etapa da Vida
Para descobrir a etapa da vida, podemos fazer o seguinte:
EtapaDaVida ev = EtapaDaVida.get(usuario.nascimento);
A primeira questão a se notar nesse código é que existe uma estrutura que representa a etapa. É bastante diferente, de termos uma String ou int para identificar essa etapa. Veja que se não tivessemos essa estrutura, é com tipos básicos que teriamos que representar essa informação no sistema. Porém, uma String não define bem o que está sendo representado. Uma String pode conter qualquer coisa, um EtapaDaVida só pode conter etapas de vida.
Nessa solução, pegamos um conceito presente no sistema e criamos uma entidade para representar esse conceito.
Veja que se quiséssemos separar os usuários por etapa de vida, e não tivéssemos essa estrutura. Teriamos um código semelhante a esse:
List<Usuario> listaJovens = new ArrayList<Usuario>();
List<Usuario> listaAdultos = new ArrayList<Usuario>();
List<Usuario> listaIdosos = new ArrayList<Usuario>();
List<Usuario> limbo = new ArrayList<Usuario>();
for (Usuario usuario : getListaUsuarios()) {
if (Utils.getIdade(usuario.nascimento) < 18) {
listaJovens.add(usuario);
} else if (Utils.getIdade(usuario.nascimento) < 55) {
listaAdultos.add(usuario);
} else if (Utils.getIdade(usuario.nascimento) < 120) {
listaIdosos.add(usuario);
} else {
limbo.add(usuario);
}
}
A estrutura que temos aqui são quatro listas de usuários.
Com o Enum:
Map<EtapaDaVida, List<Usuario>> map = new HashMap<EtapaDaVida, List<Usuario>>();
for (Usuario usuario : getListaUsuarios()) {
EtapaDaVida etapa = EtapaDaVida.get(usuario.nascimento);
List<Usuario> list = map.get(etapa);
if(list == null){
map.put(etapa, list = new ArrayList<Usuario>());
}
list.add(usuario);
}
A estrutura que temos aqui é um mapa cuja chave representa um conceito no sistema.
Podemos ainda dizer, que o segundo algoritmo nunca precisará sem modificado caso novas classificações de idade sejam adicionadas ao sistema. Basta adicionar uma nova entrada ao Enum. Diferente do primeiro caso onde o algoritmo de organização será alterado.
O Enum também facilita encontrar referencias no sistema que utilizam esse conceito.
O segundo algoritmo não possui uma sequencia de IFs para testar diversas condições. Ele possui um algoritmo que recebe uma entrada, e processa. Sem variações.
Sabe aquele erro que só acontece em determinadas condições no sistema? Então, geralmente essas condições são uma sequencia de IFs onde um caso não foi previsto. Na arquitetura proposta, não existem casos não previstos.
Classificacao
A solução dada a classificação é semelhante a solução da EtapaDeVida. Porém, a classificação pode conter qualquer condição. Por isso, foi criada uma interface Classificador. Cada enum Classificacao, possui um objeto dessa interface. Esse objeto, especifico do enum é capaz de dizer se determinado dado é da Classificacao ou não. No final das contas, pode-se utilizar o enum para dizer se o dado faz parte da classificacao. Por trás dos panos ele usa o classificador.
As vantagens apresentadas na EtapaDeVida também acontecem nessa situação. E ainda, se determinada condição de uma classificação mudar, é necessário mudar apenas um único ponto no sistema. Um ponto muito bem definido.
Pegando o gancho do colega Luiz Augusto Prado, um algoritmo deve ser uma fórmula matemática. Deve-se dar um input, que retorna um output, sem determinadas condições. É como uma máquina, que devido as suas engrenagens sempre funciona, não importando a situação. Imagine a marcha de um carro, não existem ifs perguntando a posicao da alavanca para mudar a engrenagem da marcha. A alavanca está relacionada ao cambio e a modificacao da posição implica a mudança da engrenagem (sem ifs).
Com construções avançadas, onde existe uma engrenagem no seu sistema, é possível ter muito mais segurança, e afirmar com mais precisão que um sistema é robusto. Não tem ifs… pode bater com pau que ele vai funcionar.
No dia a dia do desenvolvimento vão existir situações onde outros fatores vão importar no resultado da sua programação, principalmente o tempo. É perfeitamente normal que faça alguma solução que não seja ideal. Mas é importante saber, quando uma arquitetura pode ou precisa ser melhorada e fazer isso. Caso contrário, quando o sistema tiver já com seus anos de desenvolvimento, a zona vai ser tão grande que a manutenção fica impraticável (quem aqui conhece um sistemas desse?? rsrsrs)
Não necessariamente a solução dos 17 ifs é a utilização de uma interface Classificação. É necessário analisar os requisitos e pensar em uma solução que seja adequada e tenha bom custo x beneficio.
[/quote]
“Poxa vida ehm uôooou”. rs. 