Trabalhando com Enums e Reflection

Galera,

Eu estava querendo otimizar este código abaixo utilizando reflection para instanciar um Enum dinâmicamente.

Hoje eu tenho dois enums que possuem uma lista de unidades que eu posso utilizar para converte valores daquele mesma unidade. E eu queria utilizar uma mesma função para receber dois Enums diferentes. Como enum não extend outra classe, eu não sei como fazer uma função mais genérica para evitar repetição de código.

Alguém aí consegue me ajudar?

public class Conversor {

    public static String converte(Double valor, Object unidade) {

        if (unidade instanceof EComprimento) {
            return converte(valor, (EComprimento) unidade);
        }
        if (unidade instanceof EMassa) {
            return converte(valor, (EMassa) unidade);
        }
        return null;
    }

    public static String converte(Double valor, EComprimento unidade) {

        StringBuffer strbuf = new StringBuffer();
        Unidade u = new Unidade(valor, unidade.getUnidade());

        for (EComprimento e : EComprimento.values()) {
            u.setUnidade(e.getUnidade());
            if (e.equals(e.getPadrao())) {
                strbuf.append(e.name() + " = " + u + " " + e.getSimbolo() + " (padrão)");
            } else if (e.equals(unidade)) {
                strbuf.append(e.name() + " = " + u + " " + e.getSimbolo() + " (selecionado)");
            } else {
                strbuf.append(e.name() + " = " + u + " " + e.getSimbolo());
            }
            strbuf.append("\n");
        }

        return strbuf.toString();
    }

public static String converte(Double valor, EMassa unidade) {

        StringBuffer strbuf = new StringBuffer();
        Unidade u = new Unidade(valor, EMassa.getPadrao().getUnidade());

        for (EMassa e : EMassa.values()) {
            u.setUnidade(e.getUnidade());
            if (e.equals(e.getPadrao())) {
                strbuf.append(e.name() + " = " + u + " " + e.getSimbolo() + " (padrão)");
            } else {
                strbuf.append(e.name() + " = " + u + " " + e.getSimbolo());
            }
            strbuf.append("\n");
        }

        return strbuf.toString();
    }
}

Faça seus dois enums implementarem a mesma interface.

Eu até tentei isso, mas tem um método do Enum que complicou neste caso.

Eu tenho um método chamado getPadrao() que retorna um valor do tipo do mesmo Enum que eu estou utilizando.

Como ficaria esse método numa interface em comum com os outros Enums?

um abraço e obrigado pela resposta!!!

Ele pode retornar o tipo da interface na interface.
E em cada enum vc faz retornar o tipo do enum. Ou pode não fazer parte da interface.

Mas, uma coisa é certeza absoluta: Reflection é a pior maneira possível de resolver o problema. Você pode até evitar a duplicação do código, mas vai inserir algo mais complicado do que o código duplicado e que pode gerar erros em tempo de execução.

Posso tentar fazer a interface retornando um valor do tipo da interface.

Neste caso eu tenho que ter este método na interface para poder chamá-lo dentro do For.

Mas ai eu caio em outro problema. o método getPadrao() era estático, e numa interface eu não consigo declarar métodos estáticos.

Tem mais alguma dica quanto a isso?

um abraço,

Na realidade o seu codigo não faz nenhuma conversão. Ele apenas formata.
O que vc precisa é estratégia e não reflection

A sua enum deve ser Unidade e precisa de uma outra enum chamada Dimensao
Comprimento é dimensão, metro é unidade.
Massa é dimensão, kg é unidade.


enum Unidade {

   METRO ( Dimensao.COMPRIMENTO , "m"),
   MILIMETRO( Dimensao.COMPRIMENTO , "mm"),
   QUILOGRAMA ( Dimensao.MASSA, "kg")

   private Dimensao dim;

   private Unidade(Dimensao dim , String simbolo){
       this.dim = dim;
       this.simbolo = simbolo;
  }

   public String simbolo(){
    return simbolo;
   }

  static Unidade padrao( Dimensao dim ){
   // pode usar map para ser mais elegante
        switch (dim){
        case COMPRIMENTO:
                return METRO;
        case MASSA: 
               return QUILOGRAMA ;
      }
  }
}



public class Conversor {

    public static String converte(Double valor, Unidade unidade) {

        StringBuffer buffer = new StringBuffer()
       .append(valor)
       .append(unidade.simbolo());
  
       if (unidade.equals(Unidade.padrao(unidade.dimensao()){
          buffer.append("(padrão)")
      }

    }

}

[/quote]

Um sistema geral de unidades não usaria enum e sim classes normais.
O que vc precisa é uma implementação do padrão Quantity com Unidades.
No projeto MiddleHeaven fiz algo assim tenho objetos Quantity (Measure na realidade) unidades e dimensões. Essas são as três “coisas” necessárias a um sistema fisico de unidades . dê uma olha no projeto JScience para uma implementação semelhante.

Camarada sergiotaborda,

Vejo que vc já teve o mesmo problema que eu.

Já estou usando a biblioteca JScience neste sistema, mas eu customizei a utilização dela.

Criei alguns enums que seriam as dimensões, em seguida o enum da dimensão Comprimento:

Mas eu também tenho pra massa, tempo, velocidade, etc.

public enum EComprimento {

    METER(
    SI.METER,
    SI.METER.toString()),
    KM(
    SI.KILOMETER,
    SI.KILOMETER.toString()),
    MM(
    SI.MILLIMETER,
    SI.MILLIMETER.toString()),
    CM(
    SI.CENTIMETER,
    SI.CENTIMETER.toString()),
    FT(
    NonSI.FOOT,
    NonSI.FOOT.toString()),
    INCH(
    NonSI.INCH,
    NonSI.INCH.toString()),
    MILE(
    NonSI.MILE,
    NonSI.MILE.toString());
    private Unit unidade;
    private String simbolo;

    private EComprimento(Unit unidade, String simbolo) {
        this.unidade = unidade;
        this.simbolo = simbolo;
    }

    public static EComprimento getPadrao() {
        return METER;
    }

    public Unit getUnidade() {
        return this.unidade;
    }

    public String getSimbolo() {
        return this.simbolo;
    }

    @Override
    public String toString() {
        return this.simbolo;
    }
}

E uma classe Unidade que realmente faz a conversão:

public class Unidade {

    private Measure<Double, Dimensionless> valor;
    private Unit unidade;
    
    Unidade(Double valor, Unit unidade) {        
        this.unidade = unidade;
        this.setValor(valor);
    }

    public void setValor(Double valor) {
        this.valor = Measure.valueOf(valor, unidade);
    }

    public Double getValor() {
        return valor.doubleValue(unidade);
    }

    public void setUnidade(Unit unidade) {
        this.unidade = unidade;
        this.valor = valor.to(unidade);
    }

    public String getUnidade() {
        return this.unidade.toString();
    }

    @Override
    public String toString() {        
        return valor.getValue().toString();
    }
}

A classe Conversor que eu havia passado só faz a conversão para todas as unidades daquela dimensão. E eu tive que fazer uma função repetindo o código só mudando o tipo do Enum, o que ficou estéticamente ruim mas funciona.

Recaptulando:

  • utilizo enums para listas as unidades e retornar o objeto Unit referente àquela unidade.
  • utilizo uma classe Unidade que recebe a unidade do enum e faz a conversão do valor.
  • utlizo uma classe Conversor para listar um valor em todas as unidades disponível para aquela dimensão.

Vc concorda com a minha idéia?
Acha que posso melhorar em algum lugar?

[quote=lavaliante]Camarada sergiotaborda,

Vejo que vc já teve o mesmo problema que eu.

Já estou usando a biblioteca JScience neste sistema, mas eu customizei a utilização dela.
[/quote]

Mão vejo muita utilizada na sua customização. Aliás só lhe complica a vida
A sua classe unidade não é uma unidade é uma quantidade. (ela contem uma medida, unidades não contêm medidas)
Na realidade ela é uma Measure mutável. Isso é um pouco contra o objetivo de usar measure…
Mesmo assim vc poderia (tlv) hedar Measure e cirar um MutableMeasure…
Ainda não entendi o que vc chama de “conversão”. Conversão para mim é mudar de metros para milhas ou de kg força para newtons.

Para listar unidades pre-definidas vc pode simplesmente ter um list em uma classe de registro

class Unidades {

  Map<Dimention, List<Unit> > dimentinoUnits;
  public static List<Unit> unidades(Dimension dim){
      return dimentinoUnits.get(dim);
  }

   public static Unit unidadePadrao(Dimension dim){
     // escolha e devolva o padrão para dimensão. 
  }

}

Valor em todos as unidades possiveis


public Lister {

   public  static List<Measure> listAll (Measure root){
        List<Unit> unidades = Unidades.unidades(root.getUnit().getDimension()).
        List<Measure> converted = new ArrayList(unidades.size());
        for (Unit unidade : unidades){
             converted.add(root.to(unidade));
       }
       return converted;
  } 

}

//uso 
Measure m = Measure.valueOf("3.13", SI.METER);
 for (Measure m : Lister.listAll(m)){
         System.out.println(m);
 }

A conversão real é feita com to(unit) em measure. ou usando um converter…
Acho que não ganha nada encapsulando o JSicence, sobretudo não se usar enum.

Eu chamo de conversão o que a classe conversor faz, que é justamente isso que vc falou. COnverte de metros para kilometros, de kilometros para centimetros, etc.

Só que a classe conversão exibe todos os valores nas unidades disponíveis naquela dimensão (Comprimento, por exemplo)

E realmente a minha classe Unidade está mal batizada. O ideal seria ser mesmo Quantidade.

E eu fiquei tão obstinado em resolver isso com Enum que nem pensei em outras soluções. Vou tentar com Map conforme a sua dica e vou ver se “desencapsulo” o JScience.

um abraço,